I'm having an issue with the camera on Android, basically when I take a photo with the Camera class, the taken output picture actually shows more than the preview. The best way to put it would be the preview is cropped. How would I fix this?
I've run the app on Samsung Galaxy S4, Samsung Galaxy S3, HTC One M8 and Nexus One and all exhibit this same issue. (All running Android 4.0 ICS)
The camera preview is full screen in an Activity with no Action Bar and the status bar hidden.
The camera i'm trying to create is portrait so I've set displayOrientation to 90.
The code I use for determining preview size and output photo size is as follows (I believe the problem is most likely here somewhere):
public static Camera.Size getBestAspectPreviewSize(int displayOrientation,
int width,
int height,
Camera.Parameters parameters,
double closeEnough) {
double targetRatio=(double)width / height;
Camera.Size optimalSize=null;
double minDiff=Double.MAX_VALUE;
if (displayOrientation == 90 || displayOrientation == 270) {
targetRatio=(double)height / width;
}
List<Size> sizes=parameters.getSupportedPreviewSizes();
Collections.sort(sizes,
Collections.reverseOrder(new SizeComparator()));
for (Size size : sizes) {
double ratio=(double)size.width / size.height;
if (Math.abs(ratio - targetRatio) < minDiff) {
optimalSize=size;
minDiff=Math.abs(ratio - targetRatio);
}
if (minDiff < closeEnough) {
break;
}
}
return(optimalSize);
}
public static Camera.Size getLargestPictureSize(CameraHost host,
Camera.Parameters parameters,
boolean enforceProfile) {
Camera.Size result=null;
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
// android.util.Log.d("CWAC-Camera",
// String.format("%d x %d", size.width, size.height));
if (!enforceProfile
|| (size.height <= host.getDeviceProfile()
.getMaxPictureHeight() && size.height >= host.getDeviceProfile()
.getMinPictureHeight())) {
if (result == null) {
result=size;
}
else {
int resultArea=result.width * result.height;
int newArea=size.width * size.height;
if (newArea > resultArea) {
result=size;
}
}
}
}
if (result == null && enforceProfile) {
result=getLargestPictureSize(host, parameters, false);
}
return(result);
}
Size comparator class:
private static class SizeComparator implements
Comparator<Camera.Size> {
#Override
public int compare(Size lhs, Size rhs) {
int left=lhs.width * lhs.height;
int right=rhs.width * rhs.height;
if (left < right) {
return(-1);
}
else if (left > right) {
return(1);
}
return(0);
}
}
This is using cwac-camera and using SimpleCameraHost with no changes.
Any help is appreciated!
How would I fix this?
If you are using the full-bleed preview, the preview will be cropped in most cases, as the aspect ratio of the CameraView is unlikely to match the aspect ratio of the preview frames.
Quoting the documentation:
The original default behavior of CameraFragment and CameraView was to show the entire
preview, as supplied by the underlying Camera API. Since the aspect ratio of
the preview frames may be different than the aspect ratio of the CameraView,
this results in a "letterbox" effect, where the background will show through on
one axis on the sides.
The new default behavior is to completely fill the CameraView, at the cost of
cropping off some of the actual preview frame, what is known as "full-bleed preview"
(stealing some terminology from the world of print media).
To control this behavior:
Have your CameraHost
return true or false from useFullBleedPreview()
Or, call useFullBleedPreview()
on your SimpleCameraHost.Builder, passing in a boolean value to use by default.
Note that the pictures and videos taken by this library are unaffected by
useFullBleedPreview(). Hence, if useFullBleedPreview() returns true, the
picture or video may contain additional content on the edges that was not
visible in the preview.
Related
This doesn't make any sense to me as I said in the title. The method that should return the width returns 1920, and the method that should return the height returns 1080. Am I doing something wrong?
My camera activity is locked in portrait mode. My phone is a Samsung Note 4. Since my phone is in portrait mode and locked in that orientation, my height should be 1920 and my width 1080, however when I don't lock my camera activity in portrait mode, it still returns the wrong value, but when it is turned to landscape mode, the height is 1080 and the width is 1920.
My code:
mPreviewSize =
chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth,
rotatedHeight);
private static Size chooseOptimalSize(Size[] choices, int width, int
height) {
List<Size> bigEnough = new ArrayList<Size>();
for(Size option : choices) {
Log.d("detectivepikachu","option: h: "+option.getHeight()+" w:
"+option.getWidth());
if(option.getHeight() == option.getWidth() * height / width &&
option.getWidth() >= width && option.getHeight() >=
height) {
bigEnough.add(option);
}
}
if(bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizeByArea());
} else {
return choices[0];
}
}
You can rotate the texture, but camera hardware is still producing same images even in portrait mode. See Why camera2 supported preview size width always bigger than height? for more details.
If you use MediaRecorder to produce video, or employ ImageReader to capture the preview images on the fly, they will also arrive in the orientation dictated by camera hardware. To fit your device orientation, you must explicitly rotate them.
I have searched about fitting camera preview and surface view but I couldn't find about not stretching image while saving it in android studio.
The camera works well on showing on the surface view of the devices. The ratios of the supported screen sizes and surface view are ok.
The problem is that after capturing the image it takes more from all sides of the surface view of the screen. It just does not take the view that is shown on the surface view but more in all sides of the devices while saving the image.
The problem is only solved when height of the surface view and supported screen sizes are equal. Such as:
Supported size is:
1280/720
Surface view is:
405/720
But this leads to a problem which limits the height and width of the surface view. I want height of surface view to be longer and width to be match parent.
So, the problem is not solved without having the heights equal. I want height of the surface view to be longer and width to be match parent.
Can anyone help me with this,
Thanks everyone,
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private Context mContext;
private SurfaceHolder mHolder;
private Camera mCamera;
private List<Camera.Size> mSupportedPreviewSizes;
private Camera.Size mPreviewSize;
public int measurewidth, measureheigh;
public CameraPreview(Context context, Camera camera) {
super(context);
mContext = context;
mCamera = camera;
// supported preview sizes
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);
}
public void surfaceCreated(SurfaceHolder holder) {
// empty. surfaceChanged will take care of stuff
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + h);
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or reformatting changes here
// start preview with new settings
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width ,mPreviewSize.height); //B
// parameters.setPreviewSize(1280,720 );
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
Log.e(TAG, "REAL SCREEN SIZE => w=" + width + ", h=" + height);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
Log.e(TAG, "MPREVIEW SIZE OPTIMAL => w=" + mPreviewSize.width + ", h=" + mPreviewSize.height);
float ratio;
if(mPreviewSize.height >= mPreviewSize.width)
ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
else
ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
Log.e(TAG, "Ration => R=" + ratio);
// One of these methods should be used, second method squishes preview slightly
//setMeasuredDimension(1080, 1200);
//setMeasuredDimension(mPreviewSize.height, mPreviewSize.width); //B
setMeasuredDimension(width, (int)(width*ratio)); //B
// setMeasuredDimension(720, 1280);
// setMeasuredDimension((int) (width ), height);
measurewidth = width ; // this is for bitmap width
measureheigh = (int)(width*ratio); // this is for bitmap height
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) { // long screen
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.width - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.width - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.width - targetHeight) < minDiff) {// size.height was changed with size.width
optimalSize = size;
minDiff = Math.abs(size.width - targetHeight); // size.height was changed with size.width
}
}
}
return optimalSize;
}
}
After checking through your code (and the problem you are facing), I have decided to not focus on the coding part. Instead, I will give you an overview of what's happening between a preview and a snap (take picture).
Overview
In legacy camera API, there are 2 very important Camera.Size that you would need to take care - preview size and picture size.
From Camera.Parameters API, you are able to get 2 separate list of supported sizes by calling Parameters.getSupportedPreviewSizes() and Parameters.getSupportedPicturSizes().
As the names imply, each size list is dedicated for different purpose - preview and take picture.
Sample case
Imagine you have the list like this:
Preview size: 1920x1080 (16:9), 1280x960 (4:3)
Picture size: 3840x2160 (16:9), 1920x1440 (4:3)
Say, you want to take a picture in 3840x2160 (16:9), you call Parameters.setPictureSize(3840, 2160) to tell the camera you want to take a picture in this resolution when you call takePicture(...).
Solve equation
Now, how are you going to show a preview with the same resolution on the screen (or viewfinder)?
Yes, you guessed it - a preview size with the same aspect ratio (AR).
We need to find out the AR of the (target) picture size and find a matching preview size with the same AR and call Parameters.setPreviewSize(width, height) when we are ready.
In our case, we will select the preview size 1920x1080 as it has the same AR (16:9) as the picture size.
Different AR?
What happened if you want to take a picture of 16:9 but is setting the preview size to 4:3?
The answer is obvious, user will not be able to snap a picture in WYSIWYG style.
Points to note
Selecting a picture size and deciding on the preview size is not really difficult. Still, there are more you need to take care of to use the camera properly.
The orientation of the camera (CameraInfo.orientation) as opposed to the device natural orientation. Remember to call `Parameters.setRotation(rotation) when necessary.
In cases where the camera supports the same resolution in different orientation (eg. 1920x1080 and 1080x1920), call `Parameters.setPreviewSize(width, height) with the orientation you wish to take the picture in.
Hope this helps.
I cannot help myself anymore, I have read every thread about this on stackoverflow, but nothing would fix my problem.
I try to set up my camera preview in a FrameLayout, everything works fine. I determine the correct size for the preview with this code:
private Camera.Size getBestPreviewSize(int width, int height,
Camera.Parameters parameters) {
Camera.Size result = null;
for (Camera.Size size : parameters.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);
}
Afterwards I apply it to my camera:
Camera.Parameters params = mCamera.getParameters();
Camera.Size size = getBestPreviewSize(width, height, params);
params.setPreviewSize(size.width, size.height);
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(params);
The Preview is still distorted afterwards, and my FrameLayout, which I expected to have the same size as the Preview Size I calculated, remains Fullscreen.
Fullscreen means 1920x1200
Preview Size means 1920x1080
So what I did is I set my Size of the FrameLayout manually to the calculated Preview Size. Then, however, my Preview looks even more skewed.
I have no idea what I am doing wrong. I thought when I use a supported Preview Size, this should not happen.
UPDATE:
I ran my application on another device, there everything works fine. Can this be a hardware bug? The device that is not working for me is the Nexus 7 Tablet.
I have finally come to a solution, this is really related to the hardware. There is a bug with some devices:
Bug-Report
The workaround:
This is a known low-level issue with some devices; they require that the still picture size and the preview size have matching aspect ratios, to avoid stretching artifacts.
If possible for your application, match the aspect ratios for setPreviewSize and setPictureSize.
Hope this helps you as well!
I have found out that when using a textureview instead of a surfaceview as a camera preview (both hooked up to the camera via a mediarecorder) then the preview is much more fuzzy.
What I mean by fuzzy is that in a texture view you can see the pixels, especially when zooming. That is not the case when using a surfaceview. Why is that the case?
UPD:
Sorry,but after I re-write my shit code, the key is the preview size too small that caused "fuzziness", so you should set a reasonable preview Size,not the reason strikeout below, but auto-focus is suggested ...
Size size = getBestSupportSize(parameters.getSupportedPreviewSizes(), width, height);
parameters.setPreviewSize(size.width, size.height);
As to the method getBestSupportSize(), how to get the bestSize for your project needs, in this case, it is as large as the screen width andhe ratio is 4/3 your's may be some other, I calculate the ration dividing width/height.
private Size getBestSupportSize(List<Size> sizes, int width, int height) {
Size bestsize = sizes.get(0);
int screenWidth = getResources().getDisplayMetrics().widthPixels;
int dt = Integer.MAX_VALUE;
for (int i = sizes.size() - 1; i >= 0; i--) {
Log.d(TAG, "-index : " + i);
Size s = sizes.get(i);
if (s.width * 3.0f / 4 == s.height) {
int newDT = Math.abs(screenWidth - s.width);
if (newDT < dt && screenWidth < s.width) {
dt = newDT;
bestsize = s;
}
}
}
return bestsize;//note that if no "4/3" size supported,default return size[0]
}
So this "fuzziness" was caused by a small previewSize calcualate a best size for the camera using this getSupportedPreviewSizes() method
And I will keep the autoFocus snippet below, strikeout though, FYR if is needed.
Well i got the solution for this "fuzzy" problem,and my case is just using TextureView andsurfaceTexture to take a pic instead of old surfaceView withsurfaceHolderway.
The key is set this mCamera.autofocus(), why the pic is"fuzzy" is bacause we lack of this autoFocus setting.
like below :
mCamera.setPreviewTexture(surface);
//enable autoFocus if moving
mCamera.setAutoFocusMoveCallback(new AutoFocusMoveCallback() {
#Override
public void onAutoFocusMoving(boolean start, Camera camera) {
if (start) { //true means you are moving the camera
mCamera.autoFocus(myAutoFocus);
}
}
});
mCamera.startPreview();
The autoFocusCallback like this:
AutoFocusCallback myAutoFocus = new AutoFocusCallback() {
#Override
public void onAutoFocus(boolean success, Camera camera) {
}
};
I use Camera.Parameters#setPictureSize() in my application to get the higher resolution image for Camera#takePicture().
I use Camera.Parameters#getSupportedPictureSizes to get this size.
This works fine, except on a Sony Xperia S : in Logcat, the only thing I get after calling Camera#takePicture() is a line saying :
Error -2147483648
I finally managed to discover that the best picture size of this phone was 4000x3000, which seemed far too big for me.
Si I tried by setting the picture size manually at 1920x1080, and takePicture worked, then.
Here is my code for getting best picture size :
private Camera.Size getBestPictureSize(int width, int height,
Camera.Parameters parameters) {
Camera.Size result = null;
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
Log.d(this.getClass().getName(), "getBestPictureSize() - s(n) = "
+ size.width + "x" + size.height);
if (result == null) {
result = size;
} else {
if (size.width > result.width) {
result = size;
}
}
}
return result;
}
So, do you know a way to get the best picture size of a device, and being sure that takePicture will work as expected ?
Maybe not the best solution, but this is what I use in a project I'm working on.-
private Size getPreferredPictureSize() {
Size res = null;
List<Size> sizes = camera.getParameters().getSupportedPictureSizes();
for (Size s : sizes) {
float ratio = (float) s.width / (float) s.height;
if (ratio == defaultCameraRatio && s.height <= PHOTO_HEIGHT_THRESHOLD) {
res = s;
break;
}
}
return res;
}
Where defaultCameraRatio is the aspect ratio of the default camera resolution; you can get it like this.-
Camera.Parameters params = camera.getParameters();
defaultCameraRatio = (float) params.getPictureSize().width / (float) params.getPictureSize().height;
And PHOTO_HEIGHT_THRESHOLD is a 'reasonable' max height, such as 960. So, in summary, you will get a smaller resolution keeping the same aspect ratio as the default one.