Get full screen preview with Android camera2 - android

I'm building a custom camera using the new camera2 API. My code is based on the code sample provided by Google here.
I can't find a way to get the camera preview in full screen. In the code sample, they use ratio optimization to adapt to all screens but it's only taking around 3/4 of the screen's height.
Here is my code of AutoFitTextureView :
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* #param width Relative horizontal size
* #param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
Thank you very much for your help.

You should change measured width and height to cover full screen, not to fit the screen as below.
From:
if (width < height * mRatioWidth / mRatioHeight)
to
if (width > height * mRatioWidth / mRatioHeight)
It worked fine for me.

this is the solution for your problem. In this line the aspect ratio is set to 3/4. I changed chooseVideSize method to pick video size with hd resolution for MediaRecorder.
private static Size chooseVideoSize(Size[] choices) {
for (Size size : choices) {
// Note that it will pick only HD video size, you should create more robust solution depending on screen size and available video sizes
if (1920 == size.getWidth() && 1080 == size.getHeight()) {
return size;
}
}
Log.e(TAG, "Couldn't find any suitable video size");
return choices[choices.length - 1];
}
Then I corrected this method to pick preview size accordingly to video size aspect ratio and below is result.
private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<Size>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
double ratio = (double) h / w;
for (Size option : choices) {
double optionRatio = (double) option.getHeight() / option.getWidth();
if (ratio == optionRatio) {
bigEnough.add(option);
}
}
// Pick the smallest of those, assuming we found any
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[1];
}
}
I hope it will help you!

Related

Camera preview appears stretched

Im trying to use camera 2 with and AutoFitTextureView from the google sample.
Im setting it up like this
private void setUpCameraOutputs() {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
if (manager != null) {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
// We don't use a front facing camera in this sample.
//noinspection ConstantConditions
if (characteristics.get(LENS_FACING) == LENS_FACING_FRONT) continue;
StreamConfigurationMap map = characteristics.get(SCALER_STREAM_CONFIGURATION_MAP);
if(map != null) {
// For still image captures, we use the largest available size.
List<Size> outputSizes = Arrays.asList(map.getOutputSizes(sImageFormat));
Size largest = Collections.max(outputSizes, new CompareSizesByArea());
mImageReader = ImageReader.newInstance(640, 480, sImageFormat, 8);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), largest.getWidth(), largest.getHeight(), largest);
//mPreviewSize = new Size(largest.getWidth(), largest.getHeight());
setAspectRatio2(mPreviewSize);
Logging.e(TAG, "WIDTH: " + mPreviewSize.getWidth() + " HEIGHT: " + mPreviewSize.getHeight());
mCameraId = cameraId;
}
//return;
}
}else{
Logging.e(TAG,"No Manager");
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void setAspectRatio(){
int orientation = mContext.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mTextureView.setAspectRatio(mPreviewSize.getWidth(),mPreviewSize.getHeight());
} else {
mTextureView.setAspectRatio(mPreviewSize.getHeight(),mPreviewSize.getWidth());
}
}
private void setAspectRatio2(Size largest){
float cameraAspectRatio = (float) largest.getHeight() / largest.getWidth();
//Preparation
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE));
wm.getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
int finalWidth = screenWidth;
int finalHeight = screenHeight;
int widthDifference = 0;
int heightDifference = 0;
float screenAspectRatio = (float) screenWidth / screenHeight;
//Determines whether we crop width or crop height
if (screenAspectRatio > cameraAspectRatio) { //Keep width crop height
finalHeight = (int) (screenWidth / cameraAspectRatio);
heightDifference = finalHeight - screenHeight;
} else { //Keep height crop width
finalWidth = (int) (screenHeight * cameraAspectRatio);
widthDifference = finalWidth - screenWidth;
}
//Apply the result to the Preview
LayoutParams lp = (FrameLayout.LayoutParams) mTextureView.getLayoutParams();
lp.width = finalWidth;
lp.height = finalHeight;
//Below 2 lines are to center the preview, since cropping default occurs at the right and bottom
lp.leftMargin = - (widthDifference / 2);
lp.topMargin = - (heightDifference / 2);
mTextureView.setLayoutParams(lp);
}
private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
double ratio = (double) h / w;
for (Size option : choices) {
double optionRatio = (double) option.getHeight() / option.getWidth();
if (ratio == optionRatio) {
bigEnough.add(option);
}
}
// Pick the smallest of those, assuming we found any
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else {
Logging.e(TAG, "Couldn't find any suitable preview size");
return choices[1];
}
}
I've tried to set the aspect ratio with 2 different methods, but none of them seem to do what i need.
The preview takes all screen as i want, but camera preview always appear streeched, things look thiner.
I've tried a few sugestions from stackoverflow but still haven't got the perfect result.
There are a lot of libraries which ease the use of CameraView.
You can have a look at this one or this one for example.

Camera Size and 4:3 aspect ratio is not matching

I am using the Surface View of Camera to show camera and take a photo
i need the camera preview to be of specific ration 4:3, instagram is a square and mine is a rectangle.
If you look at the instagram app the camera preview is not stretching or compressed, but in mine its compressed.
This is my Camera Preview Class :
class CustomCam extends SurfaceView implements SurfaceHolder.Callback {
private final String TAG = "PIC-FRAME";
private static final double ASPECT_RATIO = 4.0 / 3.0;
private static final int PICTURE_SIZE_MAX_WIDTH = 1280;
private static final int PREVIEW_SIZE_MAX_WIDTH = 640;
private SurfaceHolder mHolder;
private Camera mCamera;
private Display display;
public List<Camera.Size> mSupportedPreviewSizes;
private Camera.Size mPreviewSize;
public CustomCam(Activity context, Camera camera) {
super(context);
mCamera = camera;
display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
for (Camera.Size str : mSupportedPreviewSizes)
Log.e(TAG, str.width + "/" + str.height);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
setKeepScreenOn(true);
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
this.getHolder().removeCallback(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
}
private Camera.Size getBestPreviewSize(int width, int height) {
Camera.Size result = null;
Camera.Parameters p = mCamera.getParameters();
for (Camera.Size size : p.getSupportedPreviewSizes()) {
if (size.width <= width && size.height <= height) {
if (result == null) {
result = size;
} else {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
}
return result;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//This line helped me set the preview Display Orientation to Portrait
//Only works API Level 8 and higher unfortunately.
try {
Camera.Parameters parameters = mCamera.getParameters();
// Camera.Size size = getBestPreviewSize(width, height);
// Camera.Size size = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
// parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
// initialCameraPictureSize(parameters);
// parameters.setPreviewSize(size.width, size.height);
Camera.Size bestPreviewSize = determineBestPreviewSize(parameters);
Camera.Size bestPictureSize = determineBestPictureSize(parameters);
parameters.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height);
parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
mCamera.setDisplayOrientation(90);
mCamera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setPreviewDisplay(mHolder);
mCamera.setParameters(parameters);
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void initialCameraPictureSize(Camera.Parameters parameters) {
List list = parameters.getSupportedPictureSizes();
if (list != null) {
Camera.Size size = null;
Iterator iterator = list.iterator();
do {
if (!iterator.hasNext())
break;
Camera.Size size1 = (Camera.Size) iterator.next();
if (Math.abs(3F * ((float) size1.width / 4F) - (float) size1.height) < 0.1F * (float) size1.width && (size == null || size1.height > size.height && size1.width < 3000))
size = size1;
} while (true);
if (size != null)
parameters.setPictureSize(size.width, size.height);
else
Log.e("CameraSettings", "No supported picture size found");
}
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
/**
* Measure the view and its content to determine the measured width and the
* measured height.
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (width > height * ASPECT_RATIO) {
width = (int) (height * ASPECT_RATIO + 0.5);
} else {
height = (int) (width / ASPECT_RATIO + 0.5);
}
setMeasuredDimension(width, height);
}
protected Camera.Size determineBestSize(List<Camera.Size> sizes, int widthThreshold) {
Camera.Size bestSize = null;
for (Camera.Size currentSize : sizes) {
boolean isDesiredRatio = (currentSize.width / 4) == (currentSize.height / 3);
boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width);
boolean isInBounds = currentSize.width <= PICTURE_SIZE_MAX_WIDTH;
if (isDesiredRatio && isInBounds && isBetterSize) {
bestSize = currentSize;
}
}
if (bestSize == null) {
return sizes.get(0);
}
return bestSize;
}
private Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
return determineBestSize(sizes, PREVIEW_SIZE_MAX_WIDTH);
}
private Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPictureSizes();
return determineBestSize(sizes, PICTURE_SIZE_MAX_WIDTH);
}
}
My Custom Frame layout :
CustomFrameLayout extends FrameLayout {
private static final float RATIO = 4f / 3f;
public CustomFrameLayout(Context context) {
super(context);
}
public CustomFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CustomFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();
int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom();
int maxWidth = (int) (heigthWithoutPadding * RATIO);
int maxHeight = (int) (widthWithoutPadding / RATIO);
if (widthWithoutPadding > maxWidth) {
width = maxWidth + getPaddingLeft() + getPaddingRight();
} else {
height = maxHeight + getPaddingTop() + getPaddingBottom();
}
setMeasuredDimension(width, height);
}
But the cam preview is compressed inside the frame layout how can i solve this ?issue ?
Update
Ok after some research got to know that its because of onMeasure
ASPECT_RATIO = 4:3
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (width > height * ASPECT_RATIO) {
width = (int) (height * ASPECT_RATIO + 0.5);
} else {
height = (int) (width / ASPECT_RATIO + 0.5);
}
setMeasuredDimension(width, height);
}
Solution
So i was thinking of a solution, might be (just like Instagram does) to make your camera at full size and then hide some areas of the layout just to make it look like its 4:3 ratio.Then by using some crop mechanism have to cut the image to make the image look like 4:3.
Say i always show preview from top with 4:3 ratio and rest of the below part is hidden, so now as soon as i take photo i want to crop the image from top to 4:3 ratio and save it.
How can i achieve this, is this a feasible solution ?
As far as I understand, your current problem is how to crop the image you receive and show it. Here is a small example:
#OnClick(R.id.btn_record_start)
public void takePhoto() {
if (null != actions) {
EasyCamera.PictureCallback callback = new EasyCamera.PictureCallback() {
public void onPictureTaken(byte[] data, EasyCamera.CameraActions actions) {
// store picture
Bitmap bitmap = ImageUtils.getExifOrientedBitmap(data);
if ((portrait && bitmap.getHeight() < bitmap.getWidth()) ||
(!portrait && bitmap.getHeight() > bitmap.getWidth())) {
Matrix matrix = new Matrix();
matrix.postRotate(90);
bitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
matrix, true);
}
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
Matrix matrix = new Matrix();
matrix.postRotate(180);
bitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
matrix, true);
}
showPhoto(bitmap);
}
};
actions.takePicture(EasyCamera.Callbacks.create()
.withJpegCallback(callback));
}
}
This is a method I'm using to handle image orientation after the photo is taken.
It can easily be modified to handle cropping too. To achieve this, you have to specify the target width and height of the image (currently I'm sending the whole bitmap's size). A possible solution is to take the image's height and delete the excessive width - so the params you send to the createBitmap method would be bitmap.getHeight() * 4.0 / 3.0 and bitmap.getHeight(). Here is the modified example:
#OnClick(R.id.btn_record_start)
public void takePhoto() {
if (null != actions) {
EasyCamera.PictureCallback callback = new EasyCamera.PictureCallback() {
public void onPictureTaken(byte[] data, EasyCamera.CameraActions actions) {
// store picture
Bitmap bitmap = ImageUtils.getExifOrientedBitmap(data);
if ((portrait && bitmap.getHeight() < bitmap.getWidth()) ||
(!portrait && bitmap.getHeight() > bitmap.getWidth())) {
Matrix matrix = new Matrix();
matrix.postRotate(90);
bitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
matrix, true);
}
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
Matrix matrix = new Matrix();
matrix.postRotate(180);
bitmap =
Bitmap.createBitmap(bitmap, 0, 0, (int) (bitmap.getHeight() * 4.0 / 3.0), bitmap.getHeight(),
matrix, true);
}
showPhoto(bitmap);
}
};
actions.takePicture(EasyCamera.Callbacks.create()
.withJpegCallback(callback));
}
}
A few things to note:
you can substitute the 4.0 / 3.0 part with the ASPECT_RATIO variable
my example is doing image rotation so it looks as it was during the preview, in your case the required UI might be different.
I'm using the EasyCamera library to simplify the camera management
Here are the other ImageUtils methods I'm using:
getExifOrientedBitmap
public static Bitmap getExifOrientedBitmap(byte[] data) {
File newPhotoFile = writeToFile(data);
if (newPhotoFile == null) {
return null;
}
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
bitmap = fixOrientationIfNeeded(newPhotoFile, bitmap);
newPhotoFile.delete();
return bitmap;
}
writeToFile
#Nullable
public static File writeToFile(byte[] data) {
File dir = PhotoMessageComposer.getPhotoDir();
if (!dir.exists()) {
dir.mkdir();
}
File newPhotoFile = new File(dir, ImageUtils.getRandomFilename());
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(newPhotoFile);
fos.write(data);
fos.close();
} catch (Exception error) {
return null;
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return newPhotoFile;
}
getPhotoDir
#NonNull
public static File getPhotoDir() {
return new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) +
PICTURES_DIR);
}
getRandomFileName
public static String getRandomFilename() {
return UUID.randomUUID().toString() + IMAGE_EXTENSION;
}
fixOrientationIfNeeded
public static Bitmap fixOrientationIfNeeded(File sourceFile, Bitmap source) {
ExifInterface exif;
try {
exif = new ExifInterface(sourceFile.getAbsolutePath());
int exifOrientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
if (exifOrientation != ExifInterface.ORIENTATION_NORMAL) {
Matrix matrix = new Matrix();
int angle = findRotationAngle(exifOrientation);
matrix.postRotate(angle);
source = Bitmap.createBitmap(source, 0, 0, source.getWidth(),
source.getHeight(), matrix, true);
return source;
}
} catch (IOException e) {
e.printStackTrace();
}
return source;
}
findRotationAngle
protected static int findRotationAngle(int exifOrientation) {
switch (exifOrientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
default:
return 0;
}
}
P.S. It has been a few years since this ImageUtils class was implemented, so probably there are better ways to handle some of these operations. They should be good enough for a starting point though.

Android camera2 surface with size 1920x1080 is not valid

I've implemented camera2 in my app and it is working on Nexus 6P and Nexus 5. Now I'm trying to test it on other devices, and the first one I tried on failed straight away. This is the error I get on HTC M7 running Lollipop:
Surface with size (w=1920, h=1080) and format 0x1 is not valid,
size not in valid set: [1920x1088, 1440x1088, 1456x832, 1088x1088,
1280x720, 960x720, 960x544, 720x720, 800x480, 768x464, 720x480,
768x432, 640x480, 544x544, 576x432, 640x384, 640x368, 480x480,
480x320, 384x288, 352x288, 320x240, 240x160, 176x144]
Any suggestions what should I do in this case? I've tried calculating the nearest resolution to my TextureView (which is 1280x720) and resizing TextureView accordingly, but that doesn't look particularly nice - too much unused space... Didn't see this problem on this device using old camera and SurfaceView
EDIT:
The problem seems to be inside my TextureView. This is my code:
inside a controller I have:
TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
int width, int height) {
startLollipopPreview(width, height);
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
int width, int height) {
configureTransform(width,height);
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return true;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
private void startLollipopPreview(int width, int height) {
CameraProxy camera = getCurrentCamera();
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
mPreviewSize = camera
.getOptimalPreviewSize((Activity) mContext,
camera.getSupportedPreviewSizes(
width, height), (double) width / height);
mPreviewTexture.setAspectRatio(mPreviewSize.width, mPreviewSize.height);
mPreviewTexture.getSurfaceTexture().setDefaultBufferSize(mPreviewSize.width,
mPreviewSize.height);
configureTransform(mPreviewSize.width, mPreviewSize.height);
camera.setStateAndHandler(getCameraStateCallback(), mBackgroundHandler);
camera.open();
} catch (CameraHardwareException e) {
Log.e(TAG, ".surfaceCreated() - Error opening camera", e);
((Activity) mContext).finish();
} catch (InterruptedException e) {
Log.e(TAG, ".surfaceCreated() - InterruptedException opening camera", e);
}
}
configureTransform() looks exactly like the Camera2 google sample so I don't think the problem is in there.
inside my TextureView I have the following:
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
params.gravity = Gravity.CENTER_VERTICAL;
setLayoutParams(params);
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
I believe you need to set the buffer size to a supported preview size:
textureView.getSurfaceTexture().setDefaultBufferSize(1280,720);
and then you can scale the TextureView so that it fits your screen, even if the preview size is smaller. The Camera2Video sample has an example. Specifically look at configureTransform in Camera2VideoFragment:
/**
* Configures the necessary {#link android.graphics.Matrix} transformation to `mTextureView`.
* This method should not to be called until the camera preview size is determined in
* openCamera, or until the size of `mTextureView` is fixed.
*
* #param viewWidth The width of `mTextureView`
* #param viewHeight The height of `mTextureView`
*/
private void configureTransform(int viewWidth, int viewHeight) {
Activity activity = getActivity();
if (null == mTextureView || null == mPreviewSize || null == activity) {
return;
}
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
mTextureView.setTransform(matrix);
}
The problem you facing change according to emulator/device that you testing the app.
This error occurs when the devices compatible sizes for the dimension of the screen and the MediaRecorder settings are not matched.
When the camera is open (openCamera(int w, int h) method) it's getting the sizes of supported resolutions by using chooseOptimalSize(.... .....) method. But before that, it will set videoSize().
Try to Logcat to get what are the supported sizes and according to that set the mMediaRecorder.setVideoSize(w,h);

Scaling an image to be the width of a custom view

I'm working on a custom view that's giving me unexpected results. What I'm trying to make happen is to fill a picture the width of the view and scale it keeping its aspect ratio, but it's doing something weird...
Here's what my generated log is spitting out.
View size: 768x942
Pic is portrait
Original Size: 960x1280 Scaled: 768x768
Scaled bitmap: 768x768
Now let me explain whats the log is saying according to the code. First we let the view onMeasure itself and once it's done that we're allowed to grab the width and height of the view. Next we check if the picture is landscape or portrait. Then we just do the math to find the size we need to scale to. After the math is done we create a new scaled bitmap with the results. The width is right but the height should be 1024 not 768. I can't see where its messing up.
public void setBitmap(Bitmap bmp) {
this.mOriginalBitmap = bmp;
if(mHasMeasured) {
//Make sure the view has measured itself so we can grab the width and height
Log.d("", "View size: " + String.valueOf(this.mViewWidth)
+ "x" + String.valueOf(this.mViewHeight));
int reqWidth, reqHeight; //The required sizes we need
//Get the new sizes for the pic to fit in the view and keep aspect ratio
if(this.mOriginalBitmap.getWidth() > this.mOriginalBitmap.getHeight()) {
//Landscape :/
Log.d("", "Pic is Landscape");
reqHeight = this.mViewHeight;
reqWidth = (this.mOriginalBitmap.getWidth()
/ this.mOriginalBitmap.getHeight()) * reqHeight;
} else {
//Portrait :)
Log.d("", "Pic is portrait");
reqWidth = this.mViewWidth;
reqHeight = (this.mOriginalBitmap.getHeight()
/ this.mOriginalBitmap.getWidth()) * reqWidth;
}
Log.d("", "Original Size: "
+ String.valueOf(mOriginalBitmap.getWidth()) + "x"
+ String.valueOf(mOriginalBitmap.getHeight())
+ " Scaled: " + String.valueOf(reqWidth)
+ "x" + String.valueOf(reqHeight) );
this.mBmpScaledForView = Bitmap.createScaledBitmap(mOriginalBitmap,
reqWidth, reqHeight, false);
this.mSrc.top = 0;
this.mSrc.left = 0;
this.mSrc.right = this.mBmpScaledForView.getWidth();
this.mSrc.bottom = this.mBmpScaledForView.getHeight();
this.mDst = this.mSrc;
Log.d("", "Scaled bitmap : "
+ String.valueOf(this.mBmpScaledForView.getWidth())
+ "x" + String.valueOf(this.mBmpScaledForView.getHeight()));
}
}
The issue here is with the image ratio computation. Bitmap.getHeight() and getWidth() return ints, which makes the result of
(this.mOriginalBitmap.getHeight()
/ this.mOriginalBitmap.getWidth())
be 1 for an image that is 960x1280.
There are several options around this. You can make a float division by converting the return value of getHeight() and getWidth():
((float) this.mOriginalBitmap.getHeight()
/ (float) this.mOriginalBitmap.getWidth())
Or you can simply start by the multiplication :
reqHeight = (reqWidth * this.mOriginalBitmap.getHeight()) / this.mOriginalBitmap.getWidth();
package com.riktamtech.app.utils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* ImageView that keeps aspect ratio when scaled
*/
public class CustomImageView extends ImageView {
public CustomImageView(Context context) {
super(context);
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
Drawable drawable = getDrawable();
if (drawable == null) {
setMeasuredDimension(0, 0);
} else {
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
if (measuredHeight == 0 && measuredWidth == 0) { // Height and
// width set
// to
// wrap_content
setMeasuredDimension(measuredWidth, measuredHeight);
} else if (measuredHeight == 0) { // Height set to wrap_content
int width = measuredWidth;
int height = width * drawable.getIntrinsicHeight()
/ drawable.getIntrinsicWidth();
setMeasuredDimension(width, height);
} else if (measuredWidth == 0) { // Width set to wrap_content
int height = measuredHeight;
int width = height * drawable.getIntrinsicWidth()
/ drawable.getIntrinsicHeight();
setMeasuredDimension(width, height);
} else { // Width and height are explicitly set (either to
// match_parent or to exact value)
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
} catch (Exception e) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

SurfaceView camera preview squared: avoid image distortion

I have the sequent scenario: a surfaceview of square shape (on top of acvitity) and a green view below (that i will fill on future with other views). Now i would that preview of camera inside surfaceView it's squared, because i need to take squared picture, but preview is distorted (maybe because i gave preview dimension not squared). I show you code.
How i calculate dimension of square at runtime and assign dimension to green view
surfaceView = (SurfaceView) findViewById(R.id.surfaceview);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
dpHeight = displayMetrics.heightPixels;
dpWidth = displayMetrics.widthPixels;
surfaceViewDimension = dpWidth;
int borderHeight = dpHeight - dpWidth;
View bottomBorder = findViewById(R.id.bottom_border);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, borderHeight);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
bottomBorder.setLayoutParams(params);
bottomBorder.setBackgroundColor(Color.GREEN);
this is how i set camera options:
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Camera.Parameters params = mCamera.getParameters();
Camera.Size result = getBestPreviewSize(params, width, height);
params.setPreviewSize(result.width, result.height);
params.setPictureFormat(ImageFormat.JPEG);
params.setJpegQuality(100);
params.setPictureSize(dpWidth, dpWidth);
params.setRotation(90);
mCamera.setParameters(params);
mCamera.startPreview();
}
public Camera.Size getBestPreviewSize(Camera.Parameters params, int width,
int height) {
Camera.Size result = null;
for (Camera.Size size : params.getSupportedPreviewSizes()) {
Log.d("size: ", size.width + ":" + size.height);
if (size.width <= width && size.height <= height) {
if (result == null) {
result = size;
} else {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
}
return result;
}
how could i change my code for obtain a squared preview without image distortion (saved picture is not distorced, only preview)?

Categories

Resources