繁体   English   中英

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

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

背景

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

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

//<= 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%压缩率意味着什么?


编辑:我发现这个链接谈论JPEG文件的旋转而不会丢失质量和文件大小,但在Android上是否有解决方案?

这是另一个链接 ,说它是可能的,但我找不到任何允许旋转jpeg文件而不会失去其质量的库

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

方法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的jpegtran可执行文件

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

除了你不需要在ndk-build上使用obj/local/armeabi/libjpeg.a ,因为我只想要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秒。 必须有一些方法可以做。

一旦你完成设置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;

} 

为了轮换,试试这个..

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

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

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

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

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

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

暂无
暂无

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

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