最接近微信的图片压缩算法Luban

2023-10-27 18:10

本文主要是介绍最接近微信的图片压缩算法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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/287444

相关文章

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

Spring MVC 图片上传

引入需要的包 <dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-

Prompt - 将图片的表格转换成Markdown

Prompt - 将图片的表格转换成Markdown 0. 引言1. 提示词2. 原始版本 0. 引言 最近尝试将图片中的表格转换成Markdown格式,需要不断条件和优化提示词。记录一下调整好的提示词,以后在继续优化迭代。 1. 提示词 英文版本: You are an AI assistant tasked with extracting the content of

uniapp设置微信小程序的交互反馈

链接:uni.showToast(OBJECT) | uni-app官网 (dcloud.net.cn) 设置操作成功的弹窗: title是我们弹窗提示的文字 showToast是我们在加载的时候进入就会弹出的提示。 2.设置失败的提示窗口和标签 icon:'error'是设置我们失败的logo 设置的文字上限是7个文字,如果需要设置的提示文字过长就需要设置icon并给

研究人员在RSA大会上演示利用恶意JPEG图片入侵企业内网

安全研究人员Marcus Murray在正在旧金山举行的RSA大会上公布了一种利用恶意JPEG图片入侵企业网络内部Windows服务器的新方法。  攻击流程及漏洞分析 最近,安全专家兼渗透测试员Marcus Murray发现了一种利用恶意JPEG图片来攻击Windows服务器的新方法,利用该方法还可以在目标网络中进行特权提升。几天前,在旧金山举行的RSA大会上,该Marcus现场展示了攻击流程,

恶意PNG:隐藏在图片中的“恶魔”

&lt;img src=&quot;https://i-blog.csdnimg.cn/blog_migrate/bffb187dc3546c6c5c6b8aa18b34b962.jpeg&quot; title=&quot;214201hhuuhubsuyuukbfy_meitu_1_meitu_2.jpg&quot;/&gt;&lt;/strong&gt;&lt;/span&gt;&lt;

PHP抓取网站图片脚本

方法一: <?phpheader("Content-type:image/jpeg"); class download_image{function read_url($str) { $file=fopen($str,"r");$result = ''; while(!feof($file)) { $result.=fgets($file,9999); } fclose($file); re

(入门篇)JavaScript 网页设计案例浅析-简单的交互式图片轮播

网页设计已经成为了每个前端开发者的必备技能,而 JavaScript 作为前端三大基础之一,更是为网页赋予了互动性和动态效果。本篇文章将通过一个简单的 JavaScript 案例,带你了解网页设计中的一些常见技巧和技术原理。今天就说一说一个常见的图片轮播效果。相信大家在各类电商网站、个人博客或者展示页面中,都看到过这种轮播图。它的核心功能是展示多张图片,并且用户可以通过点击按钮,左右切换图片。