package com.luck.picture.lib.compress; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.ExifInterface; import android.support.annotation.NonNull; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import io.reactivex.Observable; import io.reactivex.functions.Function; import io.reactivex.schedulers.Schedulers; import static com.luck.picture.lib.compress.Preconditions.checkNotNull; /** * author:luck * project:PictureSelector * email:893855882@qq.com * data:16/12/31 */ class LubanCompresser { private static final String TAG = "Luban Compress"; private final LubanBuilder mLuban; private ByteArrayOutputStream mByteArrayOutputStream; LubanCompresser(LubanBuilder luban) { mLuban = luban; } Observable<File> singleAction(final File file) { return Observable.fromCallable(new Callable<File>() { @Override public File call() throws Exception { return compressImage(mLuban.gear, file); } }).subscribeOn(Schedulers.computation()); } Observable<List<File>> multiAction(List<File> files) { List<Observable<File>> observables = new ArrayList<>(files.size()); for (final File file : files) { observables.add(Observable.fromCallable(new Callable<File>() { @Override public File call() throws Exception { return compressImage(mLuban.gear, file); } })); } return Observable.zip(observables, new Function<Object[], List<File>>() { @Override public List<File> apply(Object[] objects) throws Exception { List<File> files = new ArrayList<>(objects.length); for (Object o : objects) { files.add((File) o); } return files; } }); // return Observable.zip(observables, new FuncN<List<File>>() { // @Override // public List<File> call(Object... args) { // List<File> files = new ArrayList<>(args.length); // for (Object o : args) { // files.add((File) o); // } // return files; // } // }).subscribeOn(Schedulers.computation()); } private File compressImage(int gear, File file) throws IOException { // 网络图片返回原图 if (file != null && file.getPath().startsWith("http")) { return file; } switch (gear) { case Luban.THIRD_GEAR: return thirdCompress(file); case Luban.CUSTOM_GEAR: return customCompress(file); case Luban.FIRST_GEAR: return firstCompress(file); default: return file; } } private File thirdCompress(@NonNull File file) throws IOException { String file_name = file.getName(); String thumb = getCacheFilePath(file_name); double size; String filePath = file.getAbsolutePath(); int angle = getImageSpinAngle(filePath); int width = getImageSize(filePath)[0]; int height = getImageSize(filePath)[1]; boolean flip = width > height; int thumbW = width % 2 == 1 ? width + 1 : width; int thumbH = height % 2 == 1 ? height + 1 : height; width = thumbW > thumbH ? thumbH : thumbW; height = thumbW > thumbH ? thumbW : thumbH; double scale = ((double) width / height); if (scale <= 1 && scale > 0.5625) { if (height < 1664) { if (file.length() / 1024 < 150) { return file; } size = (width * height) / Math.pow(1664, 2) * 150; size = size < 60 ? 60 : size; } else if (height >= 1664 && height < 4990) { thumbW = width / 2; thumbH = height / 2; size = (thumbW * thumbH) / Math.pow(2495, 2) * 300; size = size < 60 ? 60 : size; } else if (height >= 4990 && height < 10240) { thumbW = width / 4; thumbH = height / 4; size = (thumbW * thumbH) / Math.pow(2560, 2) * 300; size = size < 100 ? 100 : size; } else { int multiple = height / 1280 == 0 ? 1 : height / 1280; thumbW = width / multiple; thumbH = height / multiple; size = (thumbW * thumbH) / Math.pow(2560, 2) * 300; size = size < 100 ? 100 : size; } } else if (scale <= 0.5625 && scale > 0.5) { if (height < 1280 && file.length() / 1024 < 200) { return file; } int multiple = height / 1280 == 0 ? 1 : height / 1280; thumbW = width / multiple; thumbH = height / multiple; size = (thumbW * thumbH) / (1440.0 * 2560.0) * 400; size = size < 100 ? 100 : size; } else { int multiple = (int) Math.ceil(height / (1280.0 / scale)); thumbW = width / multiple; thumbH = height / multiple; size = ((thumbW * thumbH) / (1280.0 * (1280 / scale))) * 500; size = size < 100 ? 100 : size; } return compress(filePath, thumb, flip ? thumbH : thumbW, flip ? thumbW : thumbH, angle, (long) size); } private File firstCompress(@NonNull File file) throws IOException { int minSize = 60; int longSide = 720; int shortSide = 1280; String file_name = file.getName(); String thumbFilePath = getCacheFilePath(file_name); String filePath = file.getAbsolutePath(); long size = 0; long maxSize = file.length() / 5; int angle = getImageSpinAngle(filePath); int[] imgSize = getImageSize(filePath); int width = 0, height = 0; if (imgSize[0] <= imgSize[1]) { double scale = (double) imgSize[0] / (double) imgSize[1]; if (scale <= 1.0 && scale > 0.5625) { width = imgSize[0] > shortSide ? shortSide : imgSize[0]; height = width * imgSize[1] / imgSize[0]; size = minSize; } else if (scale <= 0.5625) { height = imgSize[1] > longSide ? longSide : imgSize[1]; width = height * imgSize[0] / imgSize[1]; size = maxSize; } } else { double scale = (double) imgSize[1] / (double) imgSize[0]; if (scale <= 1.0 && scale > 0.5625) { height = imgSize[1] > shortSide ? shortSide : imgSize[1]; width = height * imgSize[0] / imgSize[1]; size = minSize; } else if (scale <= 0.5625) { width = imgSize[0] > longSide ? longSide : imgSize[0]; height = width * imgSize[1] / imgSize[0]; size = maxSize; } } return compress(filePath, thumbFilePath, width, height, angle, size); } private File customCompress(@NonNull File file) throws IOException { String fileName = file.getName(); String thumbFilePath = getCacheFilePath(fileName); String filePath = file.getAbsolutePath(); int angle = getImageSpinAngle(filePath); long fileSize = mLuban.maxSize > 0 && mLuban.maxSize < file.length() / 1024 ? mLuban.maxSize : file.length() / 1024; int[] size = getImageSize(filePath); int width = size[0]; int height = size[1]; if (mLuban.maxSize > 0 && mLuban.maxSize < file.length() / 1024f) { // find a suitable size float scale = (float) Math.sqrt(file.length() / 1024f / mLuban.maxSize); width = (int) (width / scale); height = (int) (height / scale); } // check the width&height if (mLuban.maxWidth > 0) { width = Math.min(width, mLuban.maxWidth); } if (mLuban.maxHeight > 0) { height = Math.min(height, mLuban.maxHeight); } float scale = Math.min((float) width / size[0], (float) height / size[1]); width = (int) (size[0] * scale); height = (int) (size[1] * scale); // 不压缩 if (mLuban.maxSize > file.length() / 1024f && scale == 1) { return file; } return compress(filePath, thumbFilePath, width, height, angle, fileSize); } private String getCacheFilePath(String fileName) { StringBuilder name = new StringBuilder("Luban_" + System.currentTimeMillis()); if (mLuban.compressFormat == Bitmap.CompressFormat.WEBP) { name.append(".webp"); } else if (fileName.endsWith(".webp")) { name.append(".webp"); } else if (fileName.endsWith(".png")) { name.append(".png"); } else { name.append(".jpg"); } return mLuban.cacheDir.getAbsolutePath() + File.separator + name; } /** * obtain the image's width and height * * @param imagePath the path of image */ public static int[] getImageSize(String imagePath) { int[] res = new int[2]; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; options.inSampleSize = 1; BitmapFactory.decodeFile(imagePath, options); res[0] = options.outWidth; res[1] = options.outHeight; return res; } /** * obtain the thumbnail that specify the size * * @param imagePath the target image path * @param width the width of thumbnail * @param height the height of thumbnail * @return {@link Bitmap} */ private Bitmap compress(String imagePath, int width, int height) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(imagePath, options); int outH = options.outHeight; int outW = options.outWidth; int inSampleSize = 1; while (outH / inSampleSize > height || outW / inSampleSize > width) { inSampleSize *= 2; } options.inSampleSize = inSampleSize; options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(imagePath, options); } /** * obtain the image rotation angle * * @param path path of target image */ private int getImageSpinAngle(String path) throws IOException { int degree = 0; 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; } return degree; } /** * 指定参数压缩图片 * create the thumbnail with the true rotate angle * * @param largeImagePath the big image path * @param thumbFilePath the thumbnail path * @param width width of thumbnail * @param height height of thumbnail * @param angle rotation angle of thumbnail * @param size the file size of image */ private File compress(String largeImagePath, String thumbFilePath, int width, int height, int angle, long size) throws IOException { Bitmap thbBitmap = compress(largeImagePath, width, height); thbBitmap = rotatingImage(angle, thbBitmap); return saveImage(thumbFilePath, thbBitmap, size); } /** * 旋转图片 * rotate the image with specified angle * * @param angle the angle will be rotating 旋转的角度 * @param bitmap target image 目标图片 */ private static Bitmap rotatingImage(int angle, Bitmap bitmap) { //rotate image Matrix matrix = new Matrix(); matrix.postRotate(angle); //create a new image return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } /** * 保存图片到指定路径 * Save image with specified size * * @param filePath the image file save path 储存路径 * @param bitmap the image what be save 目标图片 * @param size the file size of image 期望大小 */ private File saveImage(String filePath, Bitmap bitmap, long size) throws IOException { checkNotNull(bitmap, TAG + "bitmap cannot be null"); File result = new File(filePath.substring(0, filePath.lastIndexOf("/"))); if (!result.exists() && !result.mkdirs()) { return null; } if (mByteArrayOutputStream == null) { mByteArrayOutputStream = new ByteArrayOutputStream( bitmap.getWidth() * bitmap.getHeight()); } else { mByteArrayOutputStream.reset(); } int options = 100; bitmap.compress(mLuban.compressFormat, options, mByteArrayOutputStream); while (mByteArrayOutputStream.size() / 1024 > size && options > 6) { mByteArrayOutputStream.reset(); options -= 6; bitmap.compress(mLuban.compressFormat, options, mByteArrayOutputStream); } bitmap.recycle(); FileOutputStream fos = new FileOutputStream(filePath); mByteArrayOutputStream.writeTo(fos); fos.close(); return new File(filePath); } }