文章

系统相机

系统相机

系统相机

系统相机拍照

调用系统相机拍照

拍照相关问题

有没有相机可用?

1
2
3
4
5
6
7
8
9
10
11
/**
 * 判断系统中是否存在可以启动的相机应用
 *
 * @return 存在返回true,不存在返回false
 */
public boolean hasCamera() {
    PackageManager packageManager = mActivity.getPackageManager();
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
    return list.size() > 0;
}

如果是 AndroidR 手机,需要适配软件包可见性:

1
2
3
4
5
6
<queries>
    <!-- 拍照 -->
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>
</queries>

拍出来的照片 “ 歪了 “

经常会遇到一种情况,拍照时看到照片是正的,但是当我们的 app 获取到这张照片时,却发现旋转了 90 度(也有可能是 180、270,不过 90 度比较多见,貌似都是由于手机传感器导致的)。不是所有手机都会出现这种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * 获取图片的旋转角度
 *
 * @param path 图片绝对路径
 * @return 图片的旋转角度
 */
public static int getBitmapDegree(String path) {
    int degree = 0;
    try {
        // 从指定路径下读取图片,并获取其EXIF信息
        ExifInterface exifInterface = new ExifInterface(path);
        // 获取图片的旋转信息
        int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                degree = 90;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                degree = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                degree = 270;
                break;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return degree;
}

/**
 * 将图片按照指定的角度进行旋转
 *
 * @param bitmap 需要旋转的图片
 * @param degree 指定的旋转角度
 * @return 旋转后的图片
 */
public static Bitmap rotateBitmapByDegree(Bitmap bitmap, int degree) {
    // 根据旋转角度,生成旋转矩阵
    Matrix matrix = new Matrix();
    matrix.postRotate(degree);
    // 将原始图片按照旋转矩阵进行旋转,并得到新的图片
    Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    if (bitmap != null && !bitmap.isRecycled()) {
        bitmap.recycle();
    }
    return newBitmap;
}
  1. 压缩后的图片,Exif 会丢失,需要在压缩前修正方向
  2. Fresco 等框架会解析 Exif 自动修正方向

拍完照怎么闪退了?

曾在小米和魅族的某些机型上遇到过这样的问题,调用系统相机拍照,拍完点击确定回到自己的 app 里面却莫名奇妙的闪退了。这种闪退有两个特点:

  1. 没有什么错误日志(有些机子啥日志都没有,有些机子会出来个空异常错误日志)
  2. 同个机子上非必现(有时候怎么拍都不闪退,有时候一拍就闪退)

可能的原因 (调起系统相机 App 的 Activity 销毁了):

  1. 有些系统厂商的 ROM 会给自带相机应用做优化,当某个 app 通过 intent 进入相机拍照界面时,系统会把这个 app 当前最上层的 Activity 销毁回收。(注意:我遇到的情况是有时候很快就回收掉,有时候怎么等也不回收,没有什么必现规律。
  2. 自己 App 调用起系统相机的 Activity 旋转销毁了

模拟复现

去到开发者选项里开启不保留活动这一项进行调试验证

解决

涉及到 Activity 被回收的问题,自然要想起 onSaveInstanceState 和 onRestoreInstanceState 这对方法。去到 onSaveInstanceState 把数据保存,并在 onRestoreInstanceState 方法中进行恢复即可

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    mRestorePhotoFile = mCapturePhotoHelper.getPhoto();
    if (mRestorePhotoFile != null) {
        outState.putSerializable(EXTRA_RESTORE_PHOTO, mRestorePhotoFile);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    mRestorePhotoFile = (File) savedInstanceState.getSerializable(EXTRA_RESTORE_PHOTO);
    mCapturePhotoHelper.setPhoto(mRestorePhotoFile);
}

图片无法显示

  • 原因:

Bitmap 太大了,无法显示

  • 日志:
1
OpenGLRenderer: Bitmap too large to be uploaded into a texture
  • 分析:

对图片的采样率 inSampleSize 做处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
 * 压缩Bitmap的大小
 *
 * @param imagePath     图片文件路径
 * @param requestWidth  压缩到想要的宽度
 * @param requestHeight 压缩到想要的高度
 * @return
 */
public static Bitmap decodeBitmapFromFile(String imagePath, int requestWidth, int requestHeight) {
    if (!TextUtils.isEmpty(imagePath)) {
        if (requestWidth <= 0 || requestHeight <= 0) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            return bitmap;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//不加载图片到内存,仅获得图片宽高
        BitmapFactory.decodeFile(imagePath, options);
        options.inSampleSize = calculateInSampleSize(options, requestWidth, requestHeight); //计算获取新的采样率
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(imagePath, options);

    } else {
        return null;
    }
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;
    Log.i(TAG, "height: " + height);
    Log.i(TAG, "width: " + width);
    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }

        long totalPixels = width * height / inSampleSize;

        final long totalReqPixelsCap = reqWidth * reqHeight * 2;

        while (totalPixels > totalReqPixelsCap) {
            inSampleSize *= 2;
            totalPixels /= 2;
        }
    }
    return inSampleSize;
}
  • 问题

做了采样处理,还是存在问题,经过调试发现 BitmapFactory.Options().outHeight/outWidht 的值一直为 -1

  • *再次解决

ExifInterface 获取图片的宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static Bitmap decodeBitmapFromFile(String imagePath, int requestWidth, int requestHeight) {
    if (!TextUtils.isEmpty(imagePath)) {
        Log.i(TAG, "requestWidth: " + requestWidth);
        Log.i(TAG, "requestHeight: " + requestHeight);
        if (requestWidth <= 0 || requestHeight <= 0) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            return bitmap;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//不加载图片到内存,仅获得图片宽高
        BitmapFactory.decodeFile(imagePath, options);
        Log.i(TAG, "original height: " + options.outHeight);
        Log.i(TAG, "original width: " + options.outWidth);
        if (options.outHeight == -1 || options.outWidth == -1) {
            try {
                ExifInterface exifInterface = new ExifInterface(imagePath);
                int height = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, ExifInterface.ORIENTATION_NORMAL);//获取图片的高度
                int width = exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, ExifInterface.ORIENTATION_NORMAL);//获取图片的宽度
                Log.i(TAG, "exif height: " + height);
                Log.i(TAG, "exif width: " + width);
                options.outWidth = width;
                options.outHeight = height;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        options.inSampleSize = calculateInSampleSize(options, requestWidth, requestHeight); //计算获取新的采样率
        Log.i(TAG, "inSampleSize: " + options.inSampleSize);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(imagePath, options);

    } else {
        return null;
    }
}

系统相机录像

  1. onActivityResult 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * 创建用来存储图片的文件,以时间来命名就不会产生命名冲突
 *
 * @return 创建的图片文件
 */
private static File createVideoFile(@NonNull Context context) {
    @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "VIDEO_" + timeStamp + "_";
    File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES);
    File imageFile = null;
    try {
        imageFile = File.createTempFile(imageFileName, ".mp4", storageDir);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return imageFile;
}

@Nullable
public static Uri takeVideo(Activity activity) {
    try {
        String filePath = createVideoFile(activity).getPath();   // 保存路径
        Uri uri = FileProviderUtils.getUriForFile(activity, new File(filePath)); // 将路径转换为Uri对象
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);  // 表示跳转至相机的录视频界面
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);    // MediaStore.EXTRA_VIDEO_QUALITY 表示录制视频的质量,从 0-1,越大表示质量越好,同时视频也越大
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);    // 表示录制完后保存的录制,如果不写,则会保存到默认的路径,在onActivityResult()的回调,通过intent.getData中返回保存的路径
        intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 30);   // 设置视频录制的最长时间
        intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);   // 设置视频录制的最长时间

        if (activity.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
            activity.startActivityForResult(intent, REQ_CODE_TAKE_VIDEO);  // 跳转
        } else {
            ToastUtils.showShort("未找到有相机应用");
        }
        return uri;
    } catch (Throwable e) {
        e.printStackTrace();
        ToastUtils.showShort("本机暂不支持录制视频,请自行录制");
    }
    return null;
}

Ref

Exif

Exif

EXIF:可交换图像文件格式(英语:Exchangeable image file format,官方简称 Exif), 是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。

包括:分辨率,旋转方向,感光度、白平衡、拍摄的光圈、焦距、分辨率、相机品牌、型号、GPS 等信息。

Exif 可以附加于 JPEG、TIFF、RIFF 等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。

下图是维基百科提供的一个 exif 图片:

在进行 Android camera 相关的开发时,对于图片数据不论是缓存在本地磁盘还是上传到后端,都需要先对图片进行压缩处理。但是 JPG(JPEG)图片在压缩后原图的 EXIF 信息也会丢失。

ExifInterface

利用 Google 提供的 android.support.media.ExifInterface 对图片的 exif 进行读写设置

ExifInterface 使用

只有 JPEG 格式的图片才会携带 exif 数据,像 PNG,WebP 这类的图片就不会有这些数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * 从给定的图片路径中读取图片的exif tag信息.
 */
public ExifInterface(String filename) throws IOException {
    // ......
    try {
        // ......
        loadAttributes(in);
    } finally {
        IoUtils.closeQuietly(in);
    }
}

/**
 * 从指定的图像文件描述符中读取Exif标签. 属性突变仅支持可写和可搜索的文件描述符. 此构造函数不会倒回给定文件描述符的偏移量。开发人员在使用后应关闭文件描述符。
 */
public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
    // ......
    try {
        in = new FileInputStream(fileDescriptor);
        loadAttributes(in);
    } finally {
        IoUtils.closeQuietly(in);
    }
}

/**
 * 从给定的输入流中读取图片的exif 信息. 对文件输入流的属性图片不支持. 开发者在使用完之后应该关闭输入流.
 */
public ExifInterface(InputStream inputStream) throws IOException {
    // ......
    loadAttributes(inputStream);
}

对于每一张 JPEG 图片都会添加默认的属性信息,包含:

  1. 图片的宽、高:TAG_IMAGE_WIDTH、TAG_IMAGE_LENGTH
  2. 图片的方向:TAG_ORIENTATION ,它的值大致有如下几个:
1
2
3
4
5
6
7
8
9
ORIENTATION_FLIP_HORIZONTAL
ORIENTATION_FLIP_VERTICAL
ORIENTATION_NORMAL
ORIENTATION_ROTATE_180
ORIENTATION_ROTATE_270
ORIENTATION_ROTATE_90
ORIENTATION_TRANSPOSE
ORIENTATION_TRANSVERSE
ORIENTATION_UNDEFINED
  1. 图片光源:TAG_LIGHT_SOURCE

获取图片 Exif 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public static String logInfo(ExifInterface exifInterface) {
    String orientation = exifInterface.getAttribute(ExifInterface.TAG_ORIENTATION);
    String dateTime = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
    String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
    String model = exifInterface.getAttribute(ExifInterface.TAG_MODEL);
    String flash = exifInterface.getAttribute(ExifInterface.TAG_FLASH);
    String imageLength = exifInterface.getAttribute(ExifInterface.TAG_IMAGE_LENGTH);
    String imageWidth = exifInterface.getAttribute(ExifInterface.TAG_IMAGE_WIDTH);
    String latitude = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
    String longitude = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
    String latitudeRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
    String longitudeRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
    String exposureTime = exifInterface.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
//        String aperture = exifInterface.getAttribute(ExifInterface.TAG_APERTURE);
    String aperture = exifInterface.getAttribute(ExifInterface.TAG_APERTURE_VALUE);
//        String isoSpeedRatings = exifInterface.getAttribute(ExifInterface.TAG_ISO);
    String isoSpeedRatings = exifInterface.getAttribute(ExifInterface.TAG_ISO_SPEED);
    String dateTimeDigitized = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        dateTimeDigitized = exifInterface.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED);
    }
    String subSecTime = exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME);
    String subSecTimeOrig = exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL);
    String subSecTimeDig = exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED);
    String altitude = exifInterface.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
    String altitudeRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
    String gpsTimeStamp = exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
    String gpsDateStamp = exifInterface.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
    String whiteBalance = exifInterface.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
    String focalLength = exifInterface.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
    String processingMethod = exifInterface.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);

    StringBuilder sb = new StringBuilder();
    sb.append("## orientation=" + orientation + "\n");
    sb.append("## dateTime=" + dateTime + "\n");
    sb.append("## make=" + make + "\n");
    sb.append("## model=" + model + "\n");
    sb.append("## flash=" + flash + "\n");
    sb.append("## imageLength=" + imageLength + "\n");
    sb.append("## imageWidth=" + imageWidth + "\n");
    sb.append("## latitude=" + latitude + "\n");
    sb.append("## longitude=" + longitude + "\n");
    sb.append("## latitudeRef=" + latitudeRef + "\n");
    sb.append("## longitudeRef=" + longitudeRef + "\n");
    sb.append("## exposureTime=" + exposureTime + "\n");
    sb.append("## aperture=" + aperture + "\n");
    sb.append("## isoSpeedRatings=" + isoSpeedRatings + "\n");
    sb.append("## dateTimeDigitized=" + dateTimeDigitized + "\n");
    sb.append("## subSecTime=" + subSecTime + "\n");
    sb.append("## subSecTimeOrig=" + subSecTimeOrig + "\n");
    sb.append("## subSecTimeDig=" + subSecTimeDig + "\n");
    sb.append("## altitude=" + altitude + "\n");
    sb.append("## altitudeRef=" + altitudeRef + "\n");
    sb.append("## gpsTimeStamp=" + gpsTimeStamp + "\n");
    sb.append("## gpsDateStamp=" + gpsDateStamp + "\n");
    sb.append("## whiteBalance=" + whiteBalance + "\n");
    sb.append("## focalLength=" + focalLength + "\n");
    sb.append("## processingMethod=" + processingMethod);
    return sb.toString();
}

Android 相机拍照方向旋转

根据 Exif 方向信息旋转图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
 * Attempt to use EXIF information on the image to rotate it. Works for external files only.
 */
private static final int ORIENTATION_USE_EXIF = -1;
/**
 * Display the image file in its native orientation.
 */
private static final int ORIENTATION_0 = 0;
/**
 * Rotate the image 90 degrees clockwise.
 */
private static final int ORIENTATION_90 = 90;
/**
 * Rotate the image 180 degrees.
 */
private static final int ORIENTATION_180 = 180;
/**
 * Rotate the image 270 degrees clockwise.
 */
private static final int ORIENTATION_270 = 270;
private static final List<Integer> VALID_ORIENTATIONS = Arrays.asList(ORIENTATION_0, ORIENTATION_90, ORIENTATION_180, ORIENTATION_270, ORIENTATION_USE_EXIF);

public static Bitmap rotatingImage(Bitmap bitmap, int angle) {
    Matrix matrix = new Matrix();

    matrix.postRotate(angle);

    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}

private static int getImageDegreeFromMediaStore(Context context, Uri sourceUri) {
    int exifOrientation = -2;
    Cursor cursor = null;
    try {
        String[] columns = {MediaStore.Images.Media.ORIENTATION};
        cursor = context.getContentResolver().query(sourceUri, columns, null, null, null);
        if (cursor != null && !cursor.moveToNext()) {
            int orientation = cursor.getInt(0);
            if (VALID_ORIENTATIONS.contains(orientation) && orientation != ORIENTATION_USE_EXIF) {
                exifOrientation = orientation;
            } else {
                Log.w(TAG, "Unsupported orientation: " + orientation);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        Log.w(TAG, "Could not get orientation of image from media store");
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return exifOrientation;
}

private static int getImageDegreeFromExif(Context context, Uri sourceUri) {
    int exifOrientation = ORIENTATION_0;
    if (context == null || sourceUri == null) {
        return exifOrientation;
    }
    InputStream inputStream = null;
    try {
        inputStream = context.getContentResolver().openInputStream(sourceUri);
        androidx.exifinterface.media.ExifInterface exifInterface = new androidx.exifinterface.media.ExifInterface(inputStream);
        int orientationAttr = exifInterface.getAttributeInt(androidx.exifinterface.media.ExifInterface.TAG_ORIENTATION, androidx.exifinterface.media.ExifInterface.ORIENTATION_NORMAL);
        if (orientationAttr == androidx.exifinterface.media.ExifInterface.ORIENTATION_NORMAL || orientationAttr == androidx.exifinterface.media.ExifInterface.ORIENTATION_UNDEFINED) {
            exifOrientation = ORIENTATION_0;
        } else if (orientationAttr == androidx.exifinterface.media.ExifInterface.ORIENTATION_ROTATE_90) {
            exifOrientation = ORIENTATION_90;
        } else if (orientationAttr == androidx.exifinterface.media.ExifInterface.ORIENTATION_ROTATE_180) {
            exifOrientation = ORIENTATION_180;
        } else if (orientationAttr == androidx.exifinterface.media.ExifInterface.ORIENTATION_ROTATE_270) {
            exifOrientation = ORIENTATION_270;
        } else {
            Log.w(TAG, "Unsupported EXIF orientation: " + orientationAttr);
        }
    } catch (Exception e) {
        e.printStackTrace();
        Log.w(TAG, "Could not get EXIF orientation of image");
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return exifOrientation;
}

public static int getExifOrientation(Context context, Uri sourceUri) {
    int exifOrientation = getImageDegreeFromMediaStore(context, sourceUri);
    if (exifOrientation == -2) {
        exifOrientation = getImageDegreeFromExif(context, sourceUri);
    }
    return exifOrientation;
}

上传图片前,先旋转图片再压缩图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private String compressImage(String input) {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(input, options);

        final int oriWidth = options.outWidth;
        final int oriHeight = options.outHeight;
        final int[] properSize = getProperSize(oriWidth, oriHeight);

        options.inJustDecodeBounds = false;
        options.inSampleSize = ImageUtils.calculateInSampleSize(options, WindowUtils.getScreenWidth(), WindowUtils.getScreenHeight());
        Bitmap src = BitmapFactory.decodeFile(input, options);
        // 根据Exif旋转
        Bitmap rotateBitmap = ImageUtils.rotatingImage(Bitmap.createScaledBitmap(src, properSize[0], properSize[1], true), ImageUtils.getExifOrientation(AppUtils.getInstance().getContext(), Uri.fromFile(new File(input))));
        LogUtils.i(UploadManager.TAG, "UploadCompressInterceptor rotatingImage:" + input + ", agree= " + ImageUtils.getExifOrientation(AppUtils.getInstance().getContext(), Uri.fromFile(new File(input))));
        if (src != rotateBitmap) {
            src.recycle();
        }

        String output = getOutPath();
        File send = new File(output);
        File parent = send.getParentFile();
        if (parent != null && !parent.exists()) {
            boolean result = parent.mkdirs();
            LogUtils.d(UploadManager.TAG, "UploadCompressInterceptor mkdirs " + parent.getAbsolutePath() + " result " + result);
        }
        // 压缩图片
        FileOutputStream out = new FileOutputStream(send);
        if (rotateBitmap.compress(Bitmap.CompressFormat.JPEG, 85, out)) {
            out.flush();
            out.close();
            return output;
        }
    } catch (Throwable e) {
        LogUtils.e(UploadManager.TAG, "UploadCompressInterceptor compressImage error", e);
    }
    return null;
}

JPEG 图片压缩后保留 Exif 信息

图片压缩后,图片的 Exif 会丢失。

https://www.jianshu.com/p/7cb710d07c8e

  1. 压缩会损失 Exif

https://www.jianshu.com/p/95cd95e961d7

图片库对旋转图片的支持

Fresco 支持根据 Exif 的 orientation 修正方向

本文由作者按照 CC BY 4.0 进行授权