1

I am making a camera app.

There has been a host of issues getting orientation right because some phones don't write EXIF orientation data. Because of this, I get the bitmap, save it (since I don't think I should read EXIF data from the byte[]), then rotate the bitmap, then save over the original file.

It works, and the the orientation issue is fixed. The problem is its taking me 25 seconds or longer on some of the top of the line phones. Can you advise why my code is so slow or advise me on how I can find the problem?

Note: If I only save the image once (i.e. with the wrong orientation) it only takes a couple seconds.

Here is my image capture callback:

private Camera.PictureCallback pictureCallback = new Camera.PictureCallback()
{
    @Override
    public void onPictureTaken(byte[] data, Camera camera)
    {
        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d("EditPhotoFragment", "Error creating media file, check storage permissions");
            return;
        }
        try
        {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.flush();
            fos.close();

            orientPicture(pictureFile);

            //TODO async
            galleryAddPic(pictureFile);
        } catch (FileNotFoundException e) {
            Log.d("EditPhotoFragment", "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d("EditPhotoFragment", "Error accessing file: " + e.getMessage());
        }
    }
};

And here is where I orient and resave the image:

private Bitmap orientPicture(File pictureFile)
    {
        Bitmap bitmap = BitmapFactory.decodeFile(pictureFile.getAbsolutePath());
        Uri uri = Uri.parse(pictureFile.toString());
        ExifInterface exif = null;
        try{
            exif = new ExifInterface(uri.getPath());

        }catch (Exception e)
        {
            e.printStackTrace();
        }
        int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
        Matrix matrix = new Matrix();
        int rotationInDegrees = 0;
        //If the orientation tag is missing need to manually rotate it by the 'default' camera
        //orientation and if its front facing need to do 360 - the camera rotation value
        if(exifOrientation == ExifInterface.ORIENTATION_UNDEFINED)//All phones in this bucket can go fuck themselves
        {
            Camera.CameraInfo info = new Camera.CameraInfo();
            if(_cameraPreview.isBackFacing())
            {
                Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
            }else
            {
                Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, info);
            }
            rotationInDegrees = info.orientation; //set it to the default camera orientation
        }else
        {
            rotationInDegrees = exifToDegrees(exifOrientation);
            if(!_cameraPreview.isBackFacing())//handle mirroring of front camera
            {
                Camera.CameraInfo info = new Camera.CameraInfo();
                Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
                rotationInDegrees = 360 - rotationInDegrees; //For the front camera doing 360 - gets the right orientation
            }
        }
        matrix.preRotate(rotationInDegrees);
        if(!_cameraPreview.isBackFacing())//mirror it
        {
            matrix.preScale(1,-1);
        }
        Bitmap adjustedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

        //This saves the proper image over top if it
        try
        {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            adjustedBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
            byte[] byteArray = stream.toByteArray();
            fos.write(byteArray);
            fos.flush();
            fos.close();
        }catch(Exception e)
        {
            e.printStackTrace();
        }


        return adjustedBitmap;
    }

SOLUTION As advised I SHOULD read the exif data which I was able to do without needing an external library thanks to this: https://stackoverflow.com/a/13581324/3324388

Community
  • 1
  • 1
Aggressor
  • 13,323
  • 24
  • 103
  • 182
  • If the code works, it might be better to place this in stackreview. – Adam May 27 '15 at 17:35
  • Have you done some profiling to determine the exact line that causes most of the slowdown? I don't see any loops, so that would be my first instinct. – smheidrich May 27 '15 at 17:37
  • `orientPicture(pictureFile); //TODO async galleryAddPic(pictureFile);` well which one takes that time? – greenapps May 27 '15 at 17:40
  • Ill take a look right now – Aggressor May 27 '15 at 17:42
  • 1
    `since I don't think I should read EXIF data from the byte[]` why not? Thats the way to go. But there is a misunderstanding at your side. If data contains orientation information (in the bytes[] or on file) then you dont have to rotate anything as it is a valid .jpg file already. Unless you really want to convert to .png. – greenapps May 27 '15 at 17:47
  • I definitely need to figure of the profiler on Android Studio, but yes I will read the byte data – Aggressor May 27 '15 at 17:50

1 Answers1

1

Can you advise why my code is so slow

Perhaps among other reasons, you are writing the image to a file, re-reading the same image from the file, doing the transform, then writing the image back out to a file. That is going to take a lot of time.

Note: If I only save the image once (i.e. with the wrong orientation) it only takes a couple seconds.

That's because you are doing a lot less work, including only ~33% of the disk I/O, and disk I/O is going to be slow.

since I don't think I should read EXIF data from the byte[]

My apologies if you were viciously attacked by a byte[] as a young child or something. However, if you want better performance, you are going to have to read the EXIF data out of the existing in-memory copy of the image.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • The truth shall set you free. I shall jump into the swampy pit of byte[] data reading haha! – Aggressor May 27 '15 at 17:49
  • 1
    @Aggressor: While `ExifInterface` in the Android SDK cannot work with `byte[]`, there is an edition of it in the AOSP Mms app that can. I [pulled out a copy of that](https://github.com/commonsguy/cwac-camera/tree/master/camera/src/com/android/mms/exif) into my camera library. – CommonsWare May 27 '15 at 17:51
  • Oh this looks intense, thanks I'll have a look. Who would have thought orientation would be so complex haha! – Aggressor May 27 '15 at 17:52
  • @Aggressor: If it makes you feel any better, it appears to be a pain with the new `android.hardware.camera2` API as well, though I am still working through that stuff. – CommonsWare May 27 '15 at 17:54
  • I would have loved to use camera2, but then only 5% of our users could take pictures :/ – Aggressor May 27 '15 at 17:55
  • 1
    I ended up using this: http://stackoverflow.com/a/13581324/3324388 and it's been working like a charm. – Aggressor May 27 '15 at 18:57
  • @Aggressor: Amazing how many EXIF implementations Google wrote... :-) Thanks! – CommonsWare May 27 '15 at 19:00