I built a custom camera using the Camera 1 API and for some reason it produces very dark pictures (only on the front camera, the back camera works perfectly fine). The camera preview shows the camera as it should, with the correct brightness - it's only when an image is captured and decoded into a bitmap does it look really dark. I have been frantically googling for a while and have found this problem reported quite a few times but can't find a working solution. The device I'm using is a Samsung J5.
CameraPreview:
class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String CAMERA = "CAMERA";
private static Camera mCamera;
private final CameraActivity cameraActivity;
private final SurfaceHolder mHolder;
public CameraPreview(Camera camera, CameraActivity cameraActivity) {
super(cameraActivity);
this.cameraActivity = cameraActivity;
mCamera = camera;
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCameraDisplayOrientation(int cameraId) {
Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
final int rotation = cameraActivity.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;
if (info.facing == cameraId) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (info.orientation - degrees + 360) % 360;
}
mCamera.setDisplayOrientation(result);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
cameraActivity.isSafeToTakePicture(true);
Camera.Parameters params = mCamera.getParameters();
// my attempt at preventing darkness
params.setExposureCompensation(params.getMaxExposureCompensation());
if(params.isAutoExposureLockSupported()) {
params.setAutoExposureLock(false);
}
mCamera.setParameters(params);
} catch (IOException e) {
Log.d(CAMERA, "An error occured when setting up the camera preview " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
}
}
On my CameraPictureCallback
(when an image is taken), I send the bytes to this method, which decodes the bytes into a bitmap, puts it in a bundle and passes it to the next fragment:
public void openFragmentWithBitmap(byte[] bytes) {
final BitmapFactory.Options scalingOptions = new BitmapFactory.Options();
final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, scalingOptions);
final Bundle bundle = new Bundle();
bundle.putParcelable(SELFIE, bitmap);
mCamera.stopPreview();
final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
final Fragment startChainFragment = new StartChainFragment();
startChainFragment.setArguments(bundle);
ft.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
.replace(R.id.rlPlaceholder, startChainFragment, StartChainFragment.TAG)
.addToBackStack(null)
.commit();
}
Am I missing a trick here? In my surfaceCreated() I set the exposure compensation to the max but this doesn't seem to have an effect. Appreciate any help.
Edit: Turns out adding a delay didn't make a difference, so here are the results (camera preview vs actual image taken):
CameraPreview:
Captured image:
The image is captured by calling mCamera.takePicture(null, null, pictureCallback)
when the capture button is clicked (the callback just transfers the bytes to the above method).
Dark images happen when the shutter speed is too fast or the aperture isn't open enough. Be careful of your camera's automatic settings. Most cameras tend not to pick the right ones by default. If your camera creates an image that is too dark, use EV to bump up the brightness.
If you find your photos to be too dark or too light it's because of incorrect exposure. Exposure is the amount of light that gets into your camera and produces the picture on the image sensor. If a photo is too dark it means it has been underexposed and if it is too light it has been overexposed.
Camera metering is based on an 18% grey standard. Very light-skinned people tend to have overexposed faces. Darker-skin people have underexposed faces. A simple trick is to slightly underexpose light skin and slightly overexpose darker skin.
This means that the camera application has malfunctioned or the lens is broken. If this is the case, take your smartphone for repair.
After all the blood, sweat and tears I found a solution to this. I noticed that the preview and final picture didn't look like they were the same resolution (you can see the bottle is wider in the preview picture than the captured picture). So I tried to make them the same or as close as possible. I call this method after startPreview()
in surfaceCreated()
:
private void loadCameraParameters() {
final Camera.Parameters camParams = mCamera.getParameters();
final Camera.Size previewSize = getOptimalPreviewSize(camParams.getSupportedPreviewSizes(), screenWidth, screenHeight);
camParams.setPreviewSize(previewSize.width, previewSize.height);
final Camera.Size pictureSize = getOptimalPreviewSize(camParams.getSupportedPictureSizes(), screenWidth, screenHeight);
camParams.setPictureSize(pictureSize.width, pictureSize.height);
mCamera.setParameters(camParams);
}
where getOptimalPreviewSize()
is:
private Camera.Size getOptimalPreviewSize(List<Size> sizes, int targetWidth, int targetHeight) {
final double aspectTolerance = 0.05;
final double targetRatio = (double) targetWidth/targetHeight;
if (sizes == null) {
return null;
}
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
final double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > aspectTolerance) 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;
}
Now my picture and preview are a perfect match on both of my devices, with the same brightness and resolution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With