本文主要是介绍最接近微信的图片压缩算法Luban,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Luban是一个国内很牛逼的图片压缩库:https://github.com/Curzibn/Luban
使用这个库有很多东西都没法自己修改了(比如压缩后图片保存的地址)。所以我把源码弄下来。自己做一个util包。用起来就很方便了。
这里把luban的代码全部弄过来。放到imageutil包里。
贴代码:
调用的时候:
Preconditions类:
final class Preconditions {/** * Ensures that an object reference passed as a parameter to the calling method is not null. * * @param reference an object reference * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ static <T> T checkNotNull(T reference) {if (reference == null) {throw new NullPointerException(); }return reference; }/** * Ensures that an object reference passed as a parameter to the calling method is not null. * * @param reference an object reference * @param errorMessage the exception message to use if the check fails; will be converted to a * string using {@link String#valueOf(Object)} * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ static <T> T checkNotNull(T reference, @Nullable Object errorMessage) {if (reference == null) {throw new NullPointerException(String.valueOf(errorMessage)); }return reference; } }
OnMultiCompressListener类:
public interface OnMultiCompressListener {/** * Fired when the compression is started, override to handle in your own code */ void onStart(); /** * Fired when a compression returns successfully, override to handle in your own code */ void onSuccess(List<File> fileList); /** * Fired when a compression fails to complete, override to handle in your own code */ void onError(Throwable e); }
OnCompressListener类
public interface OnCompressListener {/** * Fired when the compression is started, override to handle in your own code */ void onStart(); /** * Fired when a compression returns successfully, override to handle in your own code */ void onSuccess(File file); /** * Fired when a compression fails to complete, override to handle in your own code */ void onError(Throwable e); }Luban类
/** * 选择什么档位看需要--一般情况都推荐选THIRD_GEAR档 * 1.这3种档次都进行了质量压缩,要上传到服务器的话尽量选3(清晰度可以,大小可以) * 2.如果像素不能变,就只能选CUSTOM_GEAR(纯质量压缩) * 3.getCacheFilePath方法中修改图片保存路径 */public class Luban {//最后图片效果4好于3好于1 //也就是1压缩都最厉害但是不清晰了 //3也压缩的厉害清晰度也可以 //4是压缩的最少的。清晰度最好 public static final int FIRST_GEAR = 1;//质量,尺寸压缩都进行了 public static final int THIRD_GEAR = 3;//质量,尺寸压缩都进行了 public static final int CUSTOM_GEAR = 4;//只进行质量压缩,像素不变 private static final String TAG = "Luban"; private static String DEFAULT_DISK_CACHE_DIR = "luban_disk_cache"; private static volatile Luban INSTANCE; private final File mCacheDir; private OnCompressListener compressListener; private File mFile; private List<File> mFileList; private int gear = THIRD_GEAR; private String filename; private int mMaxSize; private int mMaxHeight; private int mMaxWidth; protected Luban(File cacheDir) {mCacheDir = cacheDir; }/** * Returns a directory with a default name in the private cache directory of the application to * use to store * retrieved media and thumbnails. * * @param context A context. * @see #getPhotoCacheDir(Context, String) */ private static File getPhotoCacheDir(Context context) {return getPhotoCacheDir(context, Luban.DEFAULT_DISK_CACHE_DIR); }/** * Returns a directory with the given name in the private cache directory of the application to * use to store * retrieved media and thumbnails. * * @param context A context. * @param cacheName The name of the subdirectory in which to store the cache. * @see #getPhotoCacheDir(Context) */ private static File getPhotoCacheDir(Context context, String cacheName) {File cacheDir = context.getCacheDir(); if (cacheDir != null) {File result = new File(cacheDir, cacheName); if (!result.mkdirs() && (!result.exists() || !result.isDirectory())) {// File wasn't able to create a directory, or the result exists but not a directory return null; }return result; }if (Log.isLoggable(TAG, Log.ERROR)) {Log.e(TAG, "default disk cache dir is null"); }return null; }public static Luban get(Context context) {if (INSTANCE == null) INSTANCE = new Luban(Luban.getPhotoCacheDir(context)); return INSTANCE; }public Luban clearCache() {if (mCacheDir.exists()) {deleteFile(mCacheDir); }return this; }@Deprecated public Subscription launch() {checkNotNull(mFile, "the image file cannot be null, please call .load() before this method!"); return Observable.just(mFile).map(new Func1<File, File>() {@Override public File call(File file) {return compressImage(gear, file); }}).subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()).onErrorResumeNext(Observable.<File>empty()).doOnRequest(new Action1<Long>() {@Override public void call(Long aLong) {compressListener.onStart(); }}).subscribe(new Action1<File>() {@Override public void call(File file) {if (compressListener != null) compressListener.onSuccess(file); }}, new Action1<Throwable>() {@Override public void call(Throwable throwable) {compressListener.onError(throwable); }}); }// for single file public Subscription launch(final OnCompressListener listener) {checkNotNull(listener, "the listener cannot be null !"); if (mFile == null) {if (mFileList != null && !mFileList.isEmpty()) {mFile = mFileList.get(0); } else {throw new NullPointerException("the image file cannot be null, please call .load() before this method!"); }}return Observable.just(mFile).map(new Func1<File, File>() {@Override public File call(File file) {return compressImage(gear, file); }}).subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()).onErrorResumeNext(Observable.<File>empty()).doOnRequest(new Action1<Long>() {@Override public void call(Long aLong) {listener.onStart(); }}).subscribe(new Action1<File>() {@Override public void call(File file) {listener.onSuccess(file); }}, new Action1<Throwable>() {@Override public void call(Throwable throwable) {listener.onError(throwable); }}); }// for multi file public Subscription launch(final OnMultiCompressListener listener) {checkNotNull(listener, "the listener cannot be null !"); if (mFileList == null) {if (mFile != null) {mFileList = new ArrayList<>(); mFileList.add(mFile); } else {throw new NullPointerException("the file list cannot be null, please call .load() before this method!"); }}List<Observable<File>> observables = new ArrayList<>(); for (File file : mFileList) {observables.add(Observable.just(file).map(new Func1<File, File>() {@Override public File call(File file) {return compressImage(gear, file); }})); }return Observable.merge(observables).subscribeOn(Schedulers.computation()).toSortedList(SORT_FILE).observeOn(AndroidSchedulers.mainThread()).doOnRequest(new Action1<Long>() {@Override public void call(Long aLong) {listener.onStart(); }}).subscribe(new Action1<List<File>>() {@Override public void call(List<File> fileList) {listener.onSuccess(fileList); }}, new Action1<Throwable>() {@Override public void call(Throwable throwable) {listener.onError(throwable); }}); }public Luban load(File file) {mFile = file; return this; }public Luban load(List<File> fileList) {mFileList = fileList; return this; }@Deprecated public Luban setCompressListener(OnCompressListener listener) {compressListener = listener; return this; }public Luban putGear(int gear) {this.gear = gear; return this; }public Luban setFilename(String filename) {this.filename = filename; return this; }public Luban setMaxSize(int size) {this.mMaxSize = size; return this; }public Luban setMaxWidth(int width) {this.mMaxWidth = width; return this; }public Luban setMaxHeight(int height) {this.mMaxHeight = height; return this; }public Observable<File> asObservable() {return asObservable(Schedulers.computation()); }public Observable<File> asObservable(Scheduler scheduler) {checkNotNull(mFile, "the image file cannot be null, please call .load() before this method!"); return Observable.fromCallable(new Callable<File>() {@Override public File call() throws Exception {return compressImage(gear, mFile); }}).subscribeOn(scheduler); }public Observable<List<File>> asListObservable() {return asListObservable(Schedulers.computation()); }public Observable<List<File>> asListObservable(Scheduler scheduler) {checkNotNull(mFileList, "the image list cannot be null, please call .load() before this method!"); List<Observable<File>> observables = new ArrayList<>(); for (File file : mFileList) {observables.add(Observable.just(file).map(new Func1<File, File>() {@Override public File call(File file) {return compressImage(gear, file); }})); }return Observable.merge(observables).toSortedList(SORT_FILE).subscribeOn(scheduler); }private Func2<File, File, Integer> SORT_FILE = new Func2<File, File, Integer>() {@Override public Integer call(File file, File file2) {return file.getName().compareTo(file2.getName()); }}; private File compressImage(int gear, File file) {switch (gear) {case THIRD_GEAR:return thirdCompress(file); case CUSTOM_GEAR:return customCompress(file); case FIRST_GEAR:return firstCompress(file); default:return file; }}private File thirdCompress(@NonNull File file) {String thumb = getCacheFilePath(); double size; String filePath = file.getAbsolutePath(); int angle = getImageSpinAngle(filePath); int width = getImageSize(filePath)[0]; int height = getImageSize(filePath)[1]; 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, thumbW, thumbH, angle, (long) size); }private File firstCompress(@NonNull File file) {int minSize = 60; int longSide = 720; int shortSide = 1280; String thumbFilePath = getCacheFilePath(); 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) {String thumbFilePath = getCacheFilePath(); String filePath = file.getAbsolutePath(); int angle = getImageSpinAngle(filePath); long fileSize = mMaxSize > 0 && mMaxSize < file.length() / 1024 ? mMaxSize : file.length() / 1024; int[] size = getImageSize(filePath); int width = size[0]; int height = size[1]; if (mMaxSize > 0 && mMaxSize < file.length() / 1024f) {// find a suitable size float scale = (float) Math.sqrt(file.length() / 1024f / mMaxSize); width = (int) (width / scale); height = (int) (height / scale); }// check the width&height if (mMaxWidth > 0) {width = Math.min(width, mMaxWidth); }if (mMaxHeight > 0) {height = Math.min(height, mMaxHeight); }float scale = Math.min((float) width / size[0], (float) height / size[1]); width = (int) (size[0] * scale); height = (int) (size[1] * scale); return compress(filePath, thumbFilePath, width, height, angle, fileSize); }/** * 压缩后图片路径 * @return */ private String getCacheFilePath() {String name; if (TextUtils.isEmpty(filename)) {name = System.currentTimeMillis() + ".jpg"; } else {name = filename; }return Environment.getExternalStorageDirectory().getPath() + File.separator + "rxjavaTest"+ File.separator + name; }private void deleteFile(File fileOrDirectory) {if (fileOrDirectory.isDirectory()) {for (File file : fileOrDirectory.listFiles()) {deleteFile(file); }}fileOrDirectory.delete(); }/** * obtain the image's width and height * * @param imagePath the path of image */ public 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; if (outH > height || outW > width) {int halfH = outH / 2; int halfW = outW / 2; while ((halfH / inSampleSize) > height && (halfW / inSampleSize) > width) {inSampleSize *= 2; }}options.inSampleSize = inSampleSize; options.inJustDecodeBounds = false; int heightRatio = (int) Math.ceil(options.outHeight / (float) height); int widthRatio = (int) Math.ceil(options.outWidth / (float) width); if (heightRatio > 1 || widthRatio > 1) {if (heightRatio > widthRatio) {options.inSampleSize = heightRatio; } else {options.inSampleSize = widthRatio; }}options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(imagePath, options); }/** * obtain the image rotation angle * * @param path path of target image */ private int getImageSpinAngle(String path) {int degree = 0; try {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; }/** * 指定参数压缩图片 * 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) {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) {checkNotNull(bitmap, TAG + "bitmap cannot be null"); File result = new File(filePath.substring(0, filePath.lastIndexOf("/"))); if (!result.exists() && !result.mkdirs()) return null; ByteArrayOutputStream stream = new ByteArrayOutputStream(); int options = 100; bitmap.compress(Bitmap.CompressFormat.JPEG, options, stream); while (stream.toByteArray().length / 1024 > size && options > 6) {stream.reset(); options -= 6; bitmap.compress(Bitmap.CompressFormat.JPEG, options, stream); }try {FileOutputStream fos = new FileOutputStream(filePath); fos.write(stream.toByteArray()); fos.flush(); fos.close(); } catch (IOException e) {e.printStackTrace(); }return new File(filePath); } }
这篇关于最接近微信的图片压缩算法Luban的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!