Camera preview is fine but front camera produces very dark photos
Asked Answered
I

3

9

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: enter image description here

Captured image: enter image description here

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).

Informant answered 2/12, 2018 at 21:5 Comment(12)
It's usually this.Publication
I'm not sure I follow.. Would you be able to give an example on how I would be able to implement that?Informant
My guess was that you've taken the picture immediately after opening the camera, without giving it time to "warm up". Can't tell based on the code in your question.Publication
Yes that's correct - I have tried running the takePicture() method in a handler with a delay of 500-1000ms and that does get rid of some darkness, but the picture is still too dark (i've even tried 3000ms).Informant
A delay specifically between startPreview() and onPictureTaken()?Publication
startPreview called -> imageview clicked -> take picture after x delay (onPictureTaken called)Informant
Can you post some pictures so we can judge just how dark they are... 1.) screenshot of the SurfaceView 2.) Picture without delay 3.) Picture with delayPublication
Edited my answerInformant
I'm not able to find the issue. Although I think you must've misspoke when you said "Yes that's correct" -- if you're calling takePicture when the shutter view is clicked, then it's the user who's imposed a delay, and the camera is already warmed up.Publication
Ah yes, apologies. I have just found out that this issue only exists for the front camera.Informant
Honestly, if I were in your position, I'd intercept the raw frame data with Camera.previewCallback like in this question, and try to build my image from that. It's more complicated, but the advantage is you can know that what's onscreen and what's in your Bitmap are identical.Publication
@Publication I posted the solution to this just incase you were curiousInformant
I
1

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.

Informant answered 22/12, 2018 at 2:58 Comment(1)
Interesting. It's clearly the right solution, I wonder why there doesn't seem to be anything in the docs. It may be a hardware-specific thing.Publication
H
0

As I worked with custom camera and faced lot of issues related to different devices screen sizes etc, This is the only thing worked for me so I am suggesting the following library which may help you to sort the issue. It has lot of options too.

https://github.com/florent37/CameraFragment

Humeral answered 10/12, 2018 at 11:5 Comment(0)
C
0

I am a bit late but I had the same issue and it was fixed with very minor update i-e

parameters.setPreviewSize(getWidth(),getHeight());
parameters.setPictureSize(getWidth(),getHeight());
parameters.setAutoWhiteBalanceLock(false); // the main life-saver lock ever :) 
camera.setParameters(parameters);

width and hight can be any value but the auto white balance lock was enable in your code.

Complication answered 21/7, 2020 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.