In the Mobile platform, the Camera is the most usable feature than the other features. Almost 80% of applications have embedded camera features and are used to capture images, video recording, or barcode scanning, etc. Nowadays the camera is used in a more advanced manner like Face recognition, Object recognition, Shape recognition, etc.
As a mobile application developer implementing the camera feature in the application is an easy thing. But most of the developers face the camera preview stretch issue, so we need to implement the camera feature in the most efficient way.
In one of our recent applications, we are also facing the same issue, and after spending a lot of time on the internet and reading multiple blogs found one working solution. We combined different answers to working functionality.
In this blog, we are going to solve the Camera Preview Stretch Issue, Captured Image Stretch Issue, Resize Camera Preview With Aspect Ratio, Captured Image Rotation Issue.
The most important bug in the Camera Preview Stretch Issue is the ratio given to display a camera preview. So if the preview ratio is not given in the right way then the camera preview will be going to stretch. Maybe it will work for some of the devices but definitely it will break in many of the devices. So the preview ratio plays the most important role in camera preview.
First, we will discuss how to display a Camera Preview In Full Screen? So while displaying the camera preview in full screen we need to keep in mind that most of the android devices don’t support the full-screen preview. You can see in the default camera application also has some space at the top and bottom, and they are using those spaces to display settings button and capture button.
We were also doing the same, we will find the optimal preview size to display camera preview and display in the middle of the screen. Let me share the code and we will discuss it in detail.
Related Post: Image Compression In Android
package com.vk.camerapreviewexample.camera; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.hardware.Camera; import android.os.Handler; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import com.vk.camerapreviewexample.exception.ExceptionUtility; import java.util.List; @SuppressLint("ViewConstructor") public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG = "CameraPreview"; private Context mContext; private SurfaceHolder mHolder; private Camera mCamera; public List<Camera.Size> mSupportedPreviewSizes; public List<Camera.Size> mSupportedPictureSizes; private Camera.Size mPreviewSize; public Camera.Size mPictureSize; Camera.PreviewCallback cameraPreviewCallBack; public CameraPreview(Context context, Camera camera, Camera.PreviewCallback cameraPreviewCallBack) { super(context); mContext = context; mCamera = camera; this.cameraPreviewCallBack = cameraPreviewCallBack; // supported preview sizes mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes(); for (Camera.Size str : mSupportedPreviewSizes) // supported picture sizes mSupportedPictureSizes = mCamera.getParameters().getSupportedPictureSizes(); // 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) { } public void surfaceChanged(SurfaceHolder holder, int format, int w, int 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) { ExceptionUtility.logError(TAG, "startCameraPreview", e); } // set preview size and make any resize, rotate or reformatting changes here // start preview with new settings try { Camera.Parameters parameters = mCamera.getParameters(); List<String> focusModes = parameters.getSupportedFocusModes(); if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); } parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); parameters.setPictureSize(mPictureSize.width, mPictureSize.height); mCamera.setParameters(parameters); mCamera.setDisplayOrientation(setCameraDisplayOrientation((Activity) mContext, 1)); mCamera.setPreviewDisplay(mHolder); mCamera.setPreviewCallback(cameraPreviewCallBack); mCamera.startPreview(); } catch (Exception e) { new Handler().postDelayed(() -> { try { mCamera.startPreview(); } catch (Exception ex) { ExceptionUtility.logError(TAG, "After 2000 mils startCameraPreview:::::::::::::::::::::::::", ex); } }, 2000); ExceptionUtility.logError(TAG, "startCameraPreview", e); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); if (mSupportedPreviewSizes != null) { mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height); } if (mSupportedPictureSizes != null) { mPictureSize = getOptimalPreviewSize(mSupportedPictureSizes, width, height); } if (mPreviewSize != null) { float ratio; if (mPreviewSize.height >= mPreviewSize.width) ratio = (float) mPreviewSize.height / (float) mPreviewSize.width; else ratio = (float) mPreviewSize.width / (float) mPreviewSize.height; // One of these methods should be used, second method squishes preview slightly setMeasuredDimension(width, (int) (width * ratio)); // setMeasuredDimension((int) (width * ratio), height); } } public 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.height / size.width; 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; } public int setCameraDisplayOrientation(Activity activity, int cameraId) { android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(cameraId, info); int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } int result; //int currentapiVersion = android.os.Build.VERSION.SDK_INT; // do something for phones running an SDK before lollipop if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } return (result); } public Camera.Size getPictureSize() { return mPictureSize; } }
CameraPreview.java
Created one class named CameraPreview.java extends SurfaceView implements SurfaceHolder.Callback. The first thing we are going to do is find out the supported preview sizes by
List<Camera.Size> mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
and supported picture sizes by
List<Camera.Size> mSupportedPictureSizes = mCamera.getParameters().getSupportedPictureSizes();
Now we are going to find out the Optimal preview size or the best fitting aspect ratio to display camera preview by
public 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.height / size.width; 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; }
We are going to call the getOptimalPreviewSize() function in the overridden method named onMeasure() as
Camera.Size mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height); Camera.Size mPictureSize = getOptimalPreviewSize(mSupportedPictureSizes, width, height);
And will set the camera parameters as Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); parameters.setPictureSize(mPictureSize.width, mPictureSize.height); mCamera.setParameters(parameters);
Now we are done with our camera preview and picture stretch issue. So the question is how to use our CameraPreview class?. Here is the solution <FrameLayout android:id="@+id/fl_camera_preview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:background="@color/colorBlack" > </FrameLayout> CameraPreview maPreview = new CameraPreview(context, mCamera, null); activityFullScreenPreviewBinding.flCameraPreview.addView(maPreview); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) maPreview.getLayoutParams(); params.gravity = Gravity.CENTER;
Now we will discuss the Dynamic Resize Camera Preview. So to achieve dynamic resizing of the camera preview we don’t need to change anything in our CameraPreview.java class. but her display preview ratio is very important.
We will provide the height and width in the percentage of the device pixels but that is not sufficient. We need to find the best fitting ratio of given height-width and set it to display a camera preview. So to find the ratio of given height-width we are going to use one Utility function as
public static int[] getOptimalDimensions(float mediaWidth, float mediaHeight, int width, int height) { int layoutWidth = width; int layoutHeight = height; float ratioWidth = layoutWidth / mediaWidth; float ratioHeight = layoutHeight / mediaHeight; float aspectRatio = mediaWidth / mediaHeight; if (ratioWidth > ratioHeight) { layoutWidth = (int) (layoutHeight * aspectRatio); } else { layoutHeight = (int) (layoutWidth / aspectRatio); } Log.i(TAG, "layoutWidth: " + layoutWidth); Log.i(TAG, "layoutHeight: " + layoutHeight); Log.i(TAG, "aspectRatio: " + aspectRatio); return new int[]{layoutWidth, layoutHeight}; }
getOptimalDimensions() in Utility.java class
The getOptimalDimensions() has 4 parameters. First 2 aremediaWidth and mediaHeight we will provide the device width pixels as mediaWidth and device height pixels as mediaHeight and for the next 2 parameters, we will provide the percentage pixels provided by the user as DisplayMetrics displayMetrics = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); int width = (int) (displayMetrics.widthPixels *((float)inputWidth/100)); int height = (int) (displayMetrics.heightPixels * ((float)inputHeight/100)); And we will set layout parameters as int optimalSize[] = Utility.getOptimalDimensions(mScreenWidth , mScreenHeight , width , height); activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().width = optimalSize[0]; activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().height = optimalSize[1];
Here we are done with Camera Preview Stretch Issue, Captured Image Stretch Issue, Resize Camera Preview With Aspect Ratio. Take look for ViewModel code
package com.vk.camerapreviewexample.viewmodel; import android.app.Activity; import android.content.Context; import android.hardware.Camera; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; import android.widget.Toast; import com.vk.camerapreviewexample.camera.CameraPreview; import com.vk.camerapreviewexample.exception.ExceptionUtility; import com.vk.camerapreviewexample.databinding.ActivityFullScreenPreviewBinding; import com.vk.camerapreviewexample.utility.Utility; import java.io.File; import java.io.FileOutputStream; import static com.vk.camerapreviewexample.constant.Constant.FULL_SCREEN; import static com.vk.camerapreviewexample.constant.Constant.INPUT_HEIGHT; import static com.vk.camerapreviewexample.constant.Constant.INPUT_WIDTH; public class CameraPreviewViewModel { private Camera mCamera; private String TAG = "FullScreenPreviewViewModel"; private CameraPreview maPreview; private Context context; private ActivityFullScreenPreviewBinding activityFullScreenPreviewBinding; public CameraPreviewViewModel(Context context, ActivityFullScreenPreviewBinding activityFullScreenPreviewBinding) { this.context = context; this.activityFullScreenPreviewBinding = activityFullScreenPreviewBinding; } public void startCameraPreview() { try { Bundle extras = ((Activity) context).getIntent().getExtras(); long inputWidth = 0; long inputHeight = 0; if (extras != null) { inputWidth=extras.getInt(INPUT_WIDTH,0); inputHeight=extras.getInt(INPUT_HEIGHT,0); } mCamera = getCameraInstance(); maPreview = new CameraPreview(context, mCamera, null); activityFullScreenPreviewBinding.flCameraPreview.addView(maPreview); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) maPreview.getLayoutParams(); params.gravity = Gravity.CENTER; if(inputWidth>0&&inputHeight>0){ setLayoutWH(inputWidth,inputHeight); } } catch (Exception exc) { ExceptionUtility.logError(TAG, "startCameraPreview", exc); } } private void setLayoutWH( long inputWidth,long inputHeight) { DisplayMetrics displayMetrics = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); int mScreenWidth = displayMetrics.widthPixels; int mScreenHeight = displayMetrics.heightPixels; int width = (int) (displayMetrics.widthPixels *((float)inputWidth/100)); int height = (int) (displayMetrics.heightPixels * ((float)inputHeight/100)); int optimalSize[] = Utility.getOptimalDimensions(mScreenWidth , mScreenHeight , width , height); activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().width = optimalSize[0]; activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().height = optimalSize[1]; } private Camera getCameraInstance() { if (mCamera == null) // mCamera = Camera.open(useBackCamera ? 0 : 1); mCamera = Camera.open(1); return mCamera; } public void captureImage() { activityFullScreenPreviewBinding.ivCapture.setOnClickListener((View view) -> { try { mCamera.takePicture(() -> { }, null, (final byte[] bytes, Camera camera) -> { try { mCamera.stopPreview(); saveImage(bytes); } catch (Exception e) { ExceptionUtility.logError(TAG, "takePicture", e); } }); } catch (Exception e) { ExceptionUtility.logError(TAG, "capturePhoto", e); } }); } private void saveImage(final byte[] data) { //make a new picture file try { byte[] imageData = Utility.rotateImageData((Activity) context, data, 1); File userDIR = Utility.createExternalDirectory(FULL_SCREEN); File file = new File(userDIR, System.currentTimeMillis() + ".jpeg"); FileOutputStream outPut = new FileOutputStream(file); outPut.write(imageData, 0, imageData.length); outPut.close(); Toast.makeText(context, file.getAbsolutePath(), Toast.LENGTH_SHORT).show(); ((Activity) context).finish(); } catch (Exception e) { ExceptionUtility.logError(TAG, "saveImage", e); } } }
public static byte[] rotateImageData(Activity activity, byte[] data, int cameraId) throws Exception { Bitmap imageBitmap = null; // COnverting ByteArray to Bitmap - >Rotate and Convert back to Data if (data != null) { imageBitmap = BitmapFactory.decodeByteArray(data, 0, (data != null) ? data.length : 0); if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { Matrix mtx = new Matrix(); int cameraEyeValue = setPhotoOrientation(activity, cameraId); // CameraID = 1 : front 0:back if (cameraId == 1) { // As Front camera is Mirrored so Fliping the Orientation if (cameraEyeValue == 270) { mtx.postRotate(90); } else if (cameraEyeValue == 90) { mtx.postRotate(270); } } else { mtx.postRotate(cameraEyeValue); // cameraEyeValue is default to Display Rotation } imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.getWidth(), imageBitmap.getHeight(), mtx, true); } else {// LANDSCAPE MODE //No need to reverse width and height Bitmap scaled = Bitmap.createScaledBitmap(imageBitmap, imageBitmap.getWidth(), imageBitmap.getHeight(), true); imageBitmap = scaled; } } // Converting the Die photo to Bitmap ByteArrayOutputStream stream = new ByteArrayOutputStream(); imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); return stream.toByteArray(); } private static int setPhotoOrientation(Activity activity, int cameraId) { android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(cameraId, info); int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } int result; // do something for phones running an SDK before lollipop if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } return result; }
So this is how we solved the Camera Preview Stretch Issue, Captured Image Stretch Issue, Resize Camera Preview With Aspect Ratio, Captured Image Rotation Issue. The root of all issues is the height-width ratio to display the camera preview. Download the Full source code. Happy coding….!!
How to Effectively Hire and Manage a Remote Team of Developers.
Download NowThe Mindbowser team's professionalism consistently impressed me. Their commitment to quality shone through in every aspect of the project. They truly went the extra mile, ensuring they understood our needs perfectly and were always willing to invest the time to...
CTO, New Day Therapeutics
I collaborated with Mindbowser for several years on a complex SaaS platform project. They took over a partially completed project and successfully transformed it into a fully functional and robust platform. Throughout the entire process, the quality of their work...
President, E.B. Carlson
Mindbowser and team are professional, talented and very responsive. They got us through a challenging situation with our IOT product successfully. They will be our go to dev team going forward.
Founder, Cascada
Amazing team to work with. Very responsive and very skilled in both front and backend engineering. Looking forward to our next project together.
Co-Founder, Emerge
The team is great to work with. Very professional, on task, and efficient.
Founder, PeriopMD
I can not express enough how pleased we are with the whole team. From the first call and meeting, they took our vision and ran with it. Communication was easy and everyone was flexible to our schedule. I’m excited to...
Founder, Seeke
Mindbowser has truly been foundational in my journey from concept to design and onto that final launch phase.
CEO, KickSnap
We had very close go live timeline and Mindbowser team got us live a month before.
CEO, BuyNow WorldWide
If you want a team of great developers, I recommend them for the next project.
Founder, Teach Reach
Mindbowser built both iOS and Android apps for Mindworks, that have stood the test of time. 5 years later they still function quite beautifully. Their team always met their objectives and I'm very happy with the end result. Thank you!
Founder, Mindworks
Mindbowser has delivered a much better quality product than our previous tech vendors. Our product is stable and passed Well Architected Framework Review from AWS.
CEO, PurpleAnt
I am happy to share that we got USD 10k in cloud credits courtesy of our friends at Mindbowser. Thank you Pravin and Ayush, this means a lot to us.
CTO, Shortlist
Mindbowser is one of the reasons that our app is successful. These guys have been a great team.
Founder & CEO, MangoMirror
Kudos for all your hard work and diligence on the Telehealth platform project. You made it possible.
CEO, ThriveHealth
Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.
CEO, SMILINGMIND
They were a very responsive team! Extremely easy to communicate and work with!
Founder & CEO, TotTech
We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.
Co-Founder, TEAM8s
Mindbowser was very helpful with explaining the development process and started quickly on the project.
Executive Director of Product Development, Innovation Lab
The greatest benefit we got from Mindbowser is the expertise. Their team has developed apps in all different industries with all types of social proofs.
Co-Founder, Vesica
Mindbowser is professional, efficient and thorough.
Consultant, XPRIZE
Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.
Founder, S.T.A.R.S of Wellness
Mindbowser was great; they listened to us a lot and helped us hone in on the actual idea of the app. They had put together fantastic wireframes for us.
Co-Founder, Flat Earth
Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.
Founder, Child Life On Call
The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!
CEO, SDOH2Health LLC
Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.
CEO, Stealth Startup
Mindbowser was an excellent partner in developing my fitness app. They were patient, attentive, & understood my business needs. The end product exceeded my expectations. Thrilled to share it globally.
Owner, Phalanx
Mindbowser's expertise in tech, process & mobile development made them our choice for our app. The team was dedicated to the process & delivered high-quality features on time. They also gave valuable industry advice. Highly recommend them for app development...
Co-Founder, Fox&Fork