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

时间:2014-02-05 08:25:18

标签: android image jpeg image-compression

背景

我需要旋转相机拍摄的图像,以便它们始终保持正常方向。

为此,我使用下一个代码(使用this post来获取图像方向)

//<= 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();

此处压缩率(AKA“质量”参数)为100。

问题

代码工作正常,但结果比原来大,大得多。

原始文件大约为600-700 KB,而生成的文件大约为3MB ......

即使输入文件和输出文件的格式(JPEG)相同,也是如此。

相机设置为“超精细”质量。不确定它意味着什么,但我认为它与压缩率有关。

我尝试了什么

我尝试将“filter”参数设置为false或true。两者都导致了大文件。

即使没有旋转本身(只是解码和编码),我的文件大小也会大得多......

只有当我将压缩比设置为85左右时,我才能获得类似的文件大小,但我想知道与原始文件相比质量会受到怎样的影响。

问题

为什么会发生?

有没有办法获得与输入文件完全相同的大小和质量?

使用与原始文件相同的压缩率会使它发生吗?甚至可以获得原始文件的压缩率?

拥有100%压缩率意味着什么?


编辑:我发现this link谈论JPEG文件的旋转而不会丢失质量和文件大小,但在Android上有解决方案吗?

Here's another link说这是可能的,但我找不到任何允许旋转jpeg文件而不会丢失其质量的库

4 个答案:

答案 0 :(得分:3)

我尝试了两种方法,但我发现这些方法在我的情况下花了太长时间。我仍然分享我用过的东西。

方法1:适用于Android的LLJTran

从这里获取LLJTran: https://github.com/bkhall/AndroidMediaUtil

代码:

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;
}

方法2:使用libjepg-turbo&#39; s jpegtran可执行文件

1按照此处描述的步骤操作: https://stackoverflow.com/a/12296343/1099884

除非您obj/local/armeabi/libjpeg.a上不需要ndk-build,因为我只需要jpegtran可执行文件,但不要使用libjepg.a使用JNI。

2将jpegtran可执行文件放在资产文件夹中。 代码:

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;
    }
}

不幸的是,两者都花了太长时间。在我的三星Galaxy S1上是16秒!!!!但是我发现这个应用程序(https://play.google.com/store/apps/details?id=com.lunohod.jpegtool)只需要3-4秒。必须有一些方法可以做。

答案 1 :(得分:0)

设置完bestPreviewSize后,您必须为bestPictureSize设置每个手机支持不同的图片尺寸,以便获得最佳图片质量,您必须检查支持的图片尺寸,然后将最佳尺寸设置为相机参数。您必须在曲面中设置这些参数以获得宽度和高度。将在启动时调用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();
      }
}

现在是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;

} 

答案 2 :(得分:-1)

轮换,试试这个..

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

我正在使用PNG图像并且工作正常....对于JPEG图像,请检查上面的代码。

答案 3 :(得分:-2)

100%质量率可能是比最初保存文件的设置更高的质量设置。这导致更大的尺寸,但(几乎)相同的图像。

我不确定如何获得完全相同的尺寸,也许只是将质量设置为85%(快速和脏)。

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

不确定它是如何在android中完成的,但这就是它的工作原理。