7

Background

I need to rotate images taken by the camera so that they will always have a normal orientation.

for this, I use the next code (used this post to get the image orientation)

//<= get the angle of the image , and decode the image from the file
final Matrix matrix = new Matrix();
//<= prepare the matrix based on the EXIF data (based on https://gist.github.com/9re/1990019 )
final Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),matrix,false);
bitmap.recycle();
fileOutputStream = new FileOutputStream(tempFilePath);
rotatedBitmap.compress(CompressFormat.JPEG, 100, fileOutputStream);
rotatedBitmap.recycle();

here the compression rate (AKA "quality" parameter) is 100.

The problem

The code works fine, but the result is larger than the original, much much larger.

The original file is around 600-700 KB, while the resulting file is around 3MB ...

This is even though both the input file and the output file are of the same format (JPEG).

The camera settings are at "super fine" quality. not sure what it means, but I think it has something to do with the compression ratio.

What I've tried

I've tried to set the "filter" parameter to either false or true. both resulted in large files.

Even without the rotation itself (just decode and encode), I get much larger files sizes...

Only when I've set compression ratio to around 85, I get similar files sizes, but I wonder how the quality is affected compared to the original files.

The question

Why does it occur?

Is there a way to get the exact same size and quality of the input file ?

Will using the same compression rate as the original file make it happen? Is it even possible to get the compression rate of the original file?

What does it mean to have a 100% compression rate ?


EDIT: I've found this link talking about rotation of JPEG files without losing the quality and file size , but is there a solution for it on Android ?

Here's another link that says it's possible, but I couldn't find any library that allows rotation of jpeg files without losing their quality

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    Setting the orientation tag is your best bet. The only image viewers I've seen that ignore the orientation tag are older versions of "Windows Image Viewer" (the built-in app). My C imaging library allows lossless rotation, but it's not free and will require you calling native code. – BitBank Feb 06 '14 at 00:26
  • older versions of "Windows Image Viewer" ? so windows 8 is old... :) anyway, i wish to avoid assuming how people show the images. – android developer Feb 06 '14 at 06:33
  • I am using PNG images and its working fine.... for JPEG please check the above code. – androidDev Jun 11 '14 at 09:05
  • @androidDev PNG would work fine, as they are lossless. I asked about JPEG files, which are lossy. – android developer Jun 11 '14 at 14:33
  • HI. have you solve it? I tried LLJTran but it throw exception while reading image file. – Yeung Aug 14 '14 at 06:00
  • @Yeung no. thank you for telling about this library. maybe i will try it myself. it also has a port for android: https://github.com/bkhall/AndroidMediaUtil . wonder how to use it. – android developer Aug 14 '14 at 07:30
  • @android developer I looked at [this](http://stackoverflow.com/a/15302674/1099884) to use LLJTran. And at the line `llj.read(LLJTran.READ_ALL, true);` throw exception. I changed the second param to false and it works. The output jpeg size is approximately same as origin. If process time is not the issue (2.5MB jpeg take about 10s-20s), I recommend this. I cannot use this library to check the exif data so I get the orientation from [the post](http://stackoverflow.com/a/11081918/1099884) stated in your question. – Yeung Aug 14 '14 at 08:09
  • @Yeung Thank you. Why don't you put this as an answer? also, have you checked that in terms of data, they are exactly the same? for example, you can compare pixel-by-pixel of the 2 bitmaps (before rotation and after 4 rotations or 90 degrees) . Can you please post a sample code of how to use this library (to read, rotate, and then write the rotated image) ? – android developer Aug 14 '14 at 09:08
  • Maybe [this](http://stackoverflow.com/a/18874394/1099884) can control how the camera save the photo in jpeg. I haven't try yet. – Yeung Sep 01 '14 at 10:08
  • Hello, I have followed this example here https://github.com/bkhall/AndroidMediaUtil but the problem is that its related with java.awt package. Can anyone help with the solution or some hint. Thanks – Ultimo_m Sep 12 '15 at 22:20

4 Answers4

3

I tried two methods but I found out those methods take too long in my case. I still share what I used.

Method 1: LLJTran for Android

Get the LLJTran from here: https://github.com/bkhall/AndroidMediaUtil

The code:

public static boolean rotateJpegFileBaseOnExifWithLLJTran(File imageFile, File outFile){
    try {

        int operation = 0;
        int degree = getExifRotateDegree(imageFile.getAbsolutePath());
        //int degree = 90;
        switch(degree){
            case 90:operation = LLJTran.ROT_90;break;
            case 180:operation = LLJTran.ROT_180;break;
            case 270:operation = LLJTran.ROT_270;break;
        }   
        if (operation == 0){
            Log.d(TAG, "Image orientation is already correct");
            return false;
        }

        OutputStream output = null;
        LLJTran llj = null;
        try {   
            // Transform image
            llj = new LLJTran(imageFile);
            llj.read(LLJTran.READ_ALL, false); //don't know why setting second param to true will throw exception...
            llj.transform(operation, LLJTran.OPT_DEFAULTS
                    | LLJTran.OPT_XFORM_ORIENTATION);

            // write out file
            output = new BufferedOutputStream(new FileOutputStream(outFile));
            llj.save(output, LLJTran.OPT_WRITE_ALL);
            return true;
        } catch(Exception e){
            e.printStackTrace();
            return false;
        }finally {
            if(output != null)output.close();
            if(llj != null)llj.freeMemory();
        }
    } catch (Exception e) {
        // Unable to rotate image based on EXIF data
        e.printStackTrace();
        return false;
    }
}

public static int getExifRotateDegree(String imagePath){
    try {
        ExifInterface exif;
        exif = new ExifInterface(imagePath);
        String orientstring = exif.getAttribute(ExifInterface.TAG_ORIENTATION);
        int orientation = orientstring != null ? Integer.parseInt(orientstring) : ExifInterface.ORIENTATION_NORMAL;
        if(orientation == ExifInterface.ORIENTATION_ROTATE_90) 
            return 90;
        if(orientation == ExifInterface.ORIENTATION_ROTATE_180) 
            return 180;
        if(orientation == ExifInterface.ORIENTATION_ROTATE_270) 
            return 270;
    } catch (IOException e) {
        e.printStackTrace();
    }       
    return 0;
}

Method 2: Using libjepg-turbo's jpegtran executable

1 Follow the step describe here: https://stackoverflow.com/a/12296343/1099884

Except that you don't need obj/local/armeabi/libjpeg.a on ndk-build because I only want the jpegtran executable but not mess with JNI with libjepg.a .

2 Place the jpegtran executable on asset folder. The code:

public static boolean rotateJpegFileBaseOnExifWithJpegTran(Context context, File imageFile, File outFile){
    try {

        int operation = 0;
        int degree = getExifRotateDegree(imageFile.getAbsolutePath());
        //int degree = 90;
        String exe = prepareJpegTranExe(context);
        //chmod ,otherwise  premission denied
        boolean ret = runCommand("chmod 777 "+exe); 
        if(ret == false){
            Log.d(TAG, "chmod jpegTran failed");
            return false;
        }           
        //rotate the jpeg with jpegtran
        ret = runCommand(exe+
                " -rotate "+degree+" -outfile "+outFile.getAbsolutePath()+" "+imageFile.getAbsolutePath());         

        return ret;         
    } catch (Exception e) {
        // Unable to rotate image based on EXIF data
        e.printStackTrace();
        return false;
    }
}

public static String prepareJpegTranExe(Context context){
    File exeDir = context.getDir("JpegTran", 0);
    File exe = new File(exeDir, "jpegtran");
    if(!exe.exists()){
        try {
            InputStream is = context.getAssets().open("jpegtran");
            FileOutputStream os = new FileOutputStream(exe);
            int bufferSize = 16384;
            byte[] buffer = new byte[bufferSize];
            int count;
            while ((count=is.read(buffer, 0, bufferSize))!=-1) {
                os.write(buffer, 0, count);
            }               
            is.close();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return exe.getAbsolutePath();
}

public static boolean runCommand(String cmd){
    try{
        Process process = Runtime.getRuntime().exec(cmd);

        BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
        int read;
        char[] buffer = new char[4096];
        StringBuffer output = new StringBuffer();
        while ((read = reader.read(buffer)) > 0) {
            output.append(buffer, 0, read);
        }
        reader.close();

        // Waits for the command to finish.
        process.waitFor();

        return true;            
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

Unfortunately, both take too long. It is 16 seconds on my Samsung Galaxy S1!!!! But I found out this app (https://play.google.com/store/apps/details?id=com.lunohod.jpegtool) only take 3-4 seconds. There must be some way to do.

Community
  • 1
  • 1
Yeung
  • 2,202
  • 2
  • 27
  • 50
  • Did you check the output bitmaps, that they are identical to a rotated version of the original one? – android developer Sep 01 '14 at 16:22
  • @android developer, I saved the original jpeg file and rotated 90degree 4 times file (with jepgtran) to PC and check with diffimg. It show no pixel different. – Yeung Sep 02 '14 at 03:52
  • Cool. BTW, you didn't have to use the PC, you could just store the bitmaps on the device and compare the pixels... this answer looks promising. I hope I will get the chance to check it out. For now, here's an upvote (+1) . :) – android developer Sep 02 '14 at 05:29
  • The app you've linked to doesn't exist. Have you succeeded optimizing the code and ? Can you please share it into Github? Also, can you solve it for all types of orientations? Some of them require flipping of the image... – android developer Apr 15 '18 at 22:51
0

Once you are done setting you bestPreviewSize You have to now set for bestPictureSize every phone supports different picture sizes so to get Best Picture quality you have to check supported picture sizes and then set best size to camera parameter. You have to set those parameters in surface changed to get the width and height. surfaceChanged will be called in start and thus your new parameters will be set.

 @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {


 Camera.Parameters myParameters = camera.getParameters();

        myPicSize = getBestPictureSize(width, height);

        if (myBestSize != null && myPicSize != null) {

            myParameters.setPictureSize(myPicSize.width, myPicSize.height);

            myParameters.setJpegQuality(100);
            camera.setParameters(myParameters);

            Toast.makeText(getApplicationContext(),
                    "CHANGED:Best PICTURE SIZE:\n" +
                            String.valueOf(myPicSize.width) + " ::: " + String.valueOf(myPicSize.height),
                    Toast.LENGTH_LONG).show();
      }
}

Now the getBestPictureSize ..

 private Camera.Size getBestPictureSize(int width, int height)
    {
    Camera.Size result=null;
    Camera.Parameters p = camera.getParameters();
    for (Camera.Size size : p.getSupportedPictureSizes()) {
        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;

} 
KD_
  • 31
  • 3
  • the question was about rotating JPEG files without losing quality, but that's nice too. – android developer May 14 '15 at 05:31
  • Check This For Rotating Images @androiddeveloper [link](http://stackoverflow.com/questions/15808719/controlling-the-camera-to-take-pictures-in-portrait-doesnt-rotate-the-final-ima/30217712#30217712) – KD_ May 14 '15 at 05:35
  • Again, this is a nice thing to know about camera, but the question is about an image file (that could be created via the camera). – android developer May 14 '15 at 10:43
  • If you set this rotation to camera your Image file will be automatically be rotated when it will be saved you dont have to create bitmap for your image data and apply some other rotaion method to bitmap.. – KD_ May 14 '15 at 13:15
  • The user can always use a third party camera app. I wanted to handle the image file that the camera app produces, which has its own metadata (EXIF) about how the image was captured (oritentation etc...). – android developer May 14 '15 at 14:14
-1

For rotation, try this..

final Matrix matrix = new Matrix(); 
matrix.setRotate(90): 
final Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),matrix,false);

I am using PNG images and it is working fine.... for JPEG images, please check the above code.

416E64726577
  • 2,214
  • 2
  • 23
  • 47
androidDev
  • 1,179
  • 4
  • 13
  • 31
  • What above code? Also, my question is about JPEG, as they are lossy (lose quality when compressing, compared to the original image), and how to make a file out of it without losing the quality of the original JPEG file. Your code doesn't have anything to do with any of what I asked. – android developer Jun 11 '14 at 14:32
  • @416E64726577 It's not a real answer anyway. It decodes and encodes the file again, something I specifically wrote that I don't want, as it will lose quality due to re-compression – android developer Feb 25 '19 at 13:49
-2

100% quality rate probably is a higher quality setting than the setting the files are originally saved with. This results in higher size but (almost) the same image.

I'm not sure how to get exactly the same size, maybe just setting the quality to 85% will do (Quick and Dirty).

However if you just want to rotate the pic in 90°-steps, you could edit just the JPEG-metadata without touching the pixel data itself.

Not sure how it's done in android, but this is how it works.

user3194532
  • 687
  • 7
  • 7
  • jpeg isn't lossless. it's always lossy. about editing the metadata, it won't help since I want to have all image viewers view the image correctly, and camera apps sometimes add the orientation to the metadata, so if you show the image, it won't look like what the user has done. for example, if you took an image in 90 degrees CW and the user opens the image, the image data is taken without any consideration of the orientation, so it will be rotated and the metadata will just hold the orientation of the file. a good image viewer will consider it and rotate by itself. – android developer Feb 05 '14 at 09:15
  • btw, here's a myth page about jpeg : http://graphicssoft.about.com/od/formatsjpeg/a/jpegmythsfacts.htm it includes what you wrote about 100% quality rate. – android developer Feb 05 '14 at 12:26