简体   繁体   English

如何在Android上旋转JPEG文件而不会降低质量并获得文件大小?

[英]How to rotate a JPEG file on Android without losing quality and gaining file size?

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. 这里压缩率(AKA“质量”参数)是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 ... 原始文件大约为600-700 KB,而生成的文件大约为3MB ...

This is even though both the input file and the output file are of the same format (JPEG). 即使输入文件和输出文件都具有相同的格式(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. 我试图将“filter”参数设置为false或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. 只有当我将压缩比设置为大约85时,我才会得到类似的文件大小,但我想知道与原始文件相比质量会受到什么影响。

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 ? 拥有100%压缩率意味着什么?


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 ? 编辑:我发现这个链接谈论JPEG文件的旋转而不会丢失质量和文件大小,但在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 这是另一个链接 ,说它是可能的,但我找不到任何允许旋转jpeg文件而不会失去其质量的库

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 方法1:适用于Android的LLJTran

Get the LLJTran from here: https://github.com/bkhall/AndroidMediaUtil 从这里获取LLJTran: 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 方法2:使用libjepg-turbo的jpegtran可执行文件

1 Follow the step describe here: https://stackoverflow.com/a/12296343/1099884 1按照此处描述的步骤操作: 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 . 除了你不需要在ndk-build上使用obj/local/armeabi/libjpeg.a ,因为我只想要jpegtran可执行文件但不要使用libjepg.a JNI。

2 Place the jpegtran executable on asset folder. 2将jpegtran可执行文件放在资产文件夹中。 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!!!! 在我的三星Galaxy S1上是16秒!!!! But I found out this app ( https://play.google.com/store/apps/details?id=com.lunohod.jpegtool ) only take 3-4 seconds. 但我发现这个应用程序( https://play.google.com/store/apps/details?id=com.lunohod.jpegtool )只需3-4秒。 There must be some way to do. 必须有一些方法可以做。

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. 一旦你完成设置bestPreviewSize你现在必须设置bestPictureSize每个手机支持不同的图片大小,以获得最佳图片质量,你必须检查支持的图片大小,然后设置最佳大小的相机参数。 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. 将在启动时调用surfaceChanged,从而设置新参数。

 @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 .. 现在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;

} 

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. 我正在使用PNG图像,它工作正常....对于JPEG图像,请检查上面的代码。

100% quality rate probably is a higher quality setting than the setting the files are originally saved with. 100%质量率可能是比最初保存文件的设置更高的质量设置。 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). 我不确定如何获得完全相同的尺寸,也许只是将质量设置为85%(快速和脏)。

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. 但是,如果您只想以90°步进旋转图片,则可以仅编辑JPEG元数据而无需触摸像素数据本身。

Not sure how it's done in android, but this is how it works. 不确定它是如何在android中完成的,但这就是它的工作原理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM