java图片压缩【thumbnailator版】

java工具类在请看上面的立即下载,也可以复制下面的源码,转载请注明出处,谢谢


压缩效果可观,压缩模式多种,可以单张,可以批量

使用方法看里面ThumbnailUtil.java 代码的注释,具体业务使用步骤看后面


ThumbnailUtil.java 代码

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.*;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.stream.StreamSupport;


public class ThumbnailUtil {
	
	/**
	 * 图片压缩,代码来源 net.coobird.thumbnailator包
	 *
	 * 例子:
	 *
	 * String source = "E:\\图片压缩\\图片";
	 * String traget = "E:\\图片压缩\\图片3";
	 * 将目录图片下的所有能压缩的图片使用中等质量压缩到目录图片3,文件名保持不变,输出格式为jpg
	 * ThumbnailUtil.of(new File(source).listFiles(ThumbnailUtil.readFilter())).identifyCompress(ThumbnailUtil.ratios[1])
	 *         .outputFormat("jpg").toFiles(new File(traget), null);
	 *
	 * 将目录图片下的所有能压缩的图片使用尺寸不变,质量压缩50%压缩到目录图片3,文件名和格式使用原有输入的文件名和格式输出
	 * ThumbnailUtil.of(new File(source).listFiles(ThumbnailUtil.readFilter())).scale(1D).outputQuality(0.5D)
	 *         .outputFormat(ThumbnailUtil.orgForm).toFiles(new File(traget), "");
	 *
	 * 将图片目录下的原图.jpg使用中等质量压缩到目录图片3,文件名和格式使用原有输入的文件名和格式输出
	 * String source = "E:\\图片压缩\\图片\\原图.jpg";
	 * String traget = "E:\\图片压缩\\图片3\\原图.jpg";
	 * ThumbnailUtil.of(new File(source)).identifyCompress(ThumbnailUtil.ratios[1])
	 *         .toFile(new File(traget));
	 *
	 * 将图片目录下的原图.jpg使用尺寸不压缩,质量压缩到到目标图片40%质量,文件名和格式使用输入的文件名和格式输出
	 * String source = "E:\\图片压缩\\图片\\原图.jpg";
	 * String traget = "E:\\图片压缩\\图片3\\原图.jpg";
	 * ThumbnailUtil.of(new File(source)).scale(1D).outputQuality(0.4D).toFile(new File(traget));
	 * */

    // 压缩比率, 低(原质量*0.85),中(原质量*0.7),高(原质量*0.6)
    public static String[] ratios = new String[]{"low", "medium", "high"};
    // 原始格式
    public static String orgForm = "orgForm";

    public static Builder<File> of(File... files) {
        Iterable<File> iter = Arrays.asList(files);
        return new Builder<>(iter);
    }

    public static Builder<BufferedImage> of(BufferedImage... images) {
        return new Builder<>(Arrays.asList(images));
    }

    public static Builder<InputStream> of(InputStream... inputStreams) {
        return new Builder<>(Arrays.asList(inputStreams));
    }

    public static Builder<MultipartFile> of(MultipartFile... multipartFiles) {
        return new Builder<>(Arrays.asList(multipartFiles));
    }

    public static FilenameFilter readFilter() {
        String readFormats[] = ImageIO.getReaderFormatNames();
        Set<String> readFormatSet = new HashSet<>(Arrays.asList(readFormats));
        String writeFormats[] = ImageIO.getWriterFormatNames();
        return new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                String seprator = ".";
                if (name == null || !name.contains(seprator)) {
                    return false;
                }
                String format = name.substring(name.lastIndexOf(seprator) + 1);
                return readFormatSet.contains(format);
            }
        };
    }

    public static class Builder<T> {
        // 待转换源数据
        private final Iterable<T> sources;
        // 输出格式
        private String outputFormat = null;
        //        // 原图宽
//        private int width = -1;
//        // 原图高
//        private int height = -1;
        // 压缩比率
        private String compressionRatio = null;

        // 缩放后宽
        private double scaleWidth = Double.NaN;
        // 缩放后高
        private double scaleHeight = Double.NaN;
        // 压缩质量系数 0-1之间
        private double outputQuality = Double.NaN;

        private Builder() {
            sources = null;
        }

        private Builder(Iterable<T> sources) {
            this.sources = sources;
        }

        public Builder<T> identifyCompress(String compressionRatio) {
            if (!Objects.equals(Double.NaN, scaleWidth)
                    || !Objects.equals(Double.NaN, scaleHeight)
                    || !Objects.equals(Double.NaN, outputQuality)
            ) {
                // 有设置scale和outputQuality则不使用自动压缩选项
                return this;
            } else if (null == compressionRatio) {
                this.compressionRatio = ratios[1];
                return this;
            }
            if (!Arrays.toString(ratios).contains(compressionRatio)) {
                throw new IllegalArgumentException("Unsupported compressionRatio Type.");
            }
            this.compressionRatio = compressionRatio;
            return this;
        }

        private Builder<T> identifyCompress(String compressionRatio, int width, int height) {
            if (width <= 0 || height <= 0) {
                throw new IllegalArgumentException("Width (" + width + ") and height (" + height + ") cannot be <= 0");
            }
            // 为了支持多线程压缩, 需要将可变变量直接传入方法中,不能使用共享变量返回scaleWidth和outputQuality
            if (!Objects.equals(Double.NaN, scaleWidth)
                    || !Objects.equals(Double.NaN, scaleHeight)
                    || !Objects.equals(Double.NaN, outputQuality)
            ) {
                // 有设置scale和outputQuality则不使用自动压缩选项
                return this;
            } else if (null == compressionRatio) {
                compressionRatio = ratios[1];
            }
            if (!Arrays.toString(ratios).contains(compressionRatio)) {
                throw new IllegalArgumentException("Unsupported compressionRatio Type.");
            }
            int min = width < height ? width : height;
            double offset;
            Builder builder = new Builder();
            if (Objects.equals(ratios[0], compressionRatio)) {
                // 最低压缩,图片保持原来尺寸,质量为原来的0.8
                builder.scaleWidth = builder.scaleHeight = 1.0D;
                builder.outputQuality = 0.8D;
                return builder;
            } else if (Objects.equals(ratios[1], compressionRatio)) {
                offset = 0.4D;
            } else {
                offset = 0.3D;
            }
            if (min <= 1024) {
                // 最小像素小于1024,长和宽不压缩
                builder.scaleWidth = builder.scaleHeight = 1.0D;
                builder.outputQuality = (builder.outputQuality = 0.3D + offset) <= 1 ? builder.outputQuality : 1;
            } else if (min > 1024 && min <= 3 * 1024) {
                builder.scaleHeight = (builder.scaleHeight = 0.4D + offset) <= 1 ? builder.scaleHeight : 1;
                builder.scaleWidth = builder.scaleHeight;
                builder.outputQuality = (builder.outputQuality = 0.3D + offset) <= 1 ? builder.outputQuality : 1;
            } else {
                builder.scaleHeight = (builder.scaleHeight = 2048D / min + offset) <= 1 ? builder.scaleHeight : 1;
                builder.scaleWidth = builder.scaleHeight;
                builder.outputQuality = builder.scaleHeight;
            }
            return builder;
        }

        public Builder<T> scale(double scaleWidth, double scaleHeight) {
            if (scaleWidth <= 0.0 || scaleHeight <= 0.0) {
                throw new IllegalArgumentException(
                        "The scaling factor is equal to or less than 0."
                );
            }
            if (Double.isNaN(scaleWidth) || Double.isNaN(scaleHeight)) {
                throw new IllegalArgumentException(
                        "The scaling factor is not a number."
                );
            }
            if (Double.isInfinite(scaleWidth) || Double.isInfinite(scaleHeight)) {
                throw new IllegalArgumentException(
                        "The scaling factor cannot be infinity."
                );
            }
            this.scaleWidth = scaleWidth;
            this.scaleHeight = scaleHeight;
            return this;
        }

        public Builder<T> scale(double scale) {
            return scale(scale, scale);
        }

        public Builder<T> outputQuality(double quality) {
            if (quality < 0.0f || quality > 1.0f) {
                throw new IllegalArgumentException(
                        "The quality setting must be in the range 0.0f and " +
                                "1.0f, inclusive."
                );
            }
            outputQuality = quality;
            return this;
        }

        public Builder<T> outputFormat(String formatName) {
            if (StringUtils.isEmpty(formatName)) {
                this.outputFormat = orgForm;
                return this;
            } else if (Objects.equals(orgForm, formatName)) {
                this.outputFormat = formatName;
                return this;
            }
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
            if (!writers.hasNext()) {
                throw new UnsupportedOperationException(
                        "No suitable ImageWriter found for " + formatName + "."
                );
            }
            this.outputFormat = formatName;
            return this;
        }

        private String outputFormat(T source, String formatName) throws IOException {
            if (source == null) {
                throw new IllegalArgumentException("The resource being processed is null.");
            }
            if (StringUtils.isEmpty(formatName)) {
                formatName = orgForm;
            } else if (!Objects.equals(orgForm, formatName)) {
                return formatName;
            }
            Iterator<ImageReader> iterReader = ImageIO.getImageReaders(ImageIO.createImageInputStream(source));
            if (null == iterReader || !iterReader.hasNext()) {
                throw new UnsupportedOperationException("The resource being processed is not a picture.");
            }
            formatName = iterReader.next().getFormatName();
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
            if (!writers.hasNext()) {
                throw new UnsupportedOperationException(
                        "No suitable ImageWriter found for " + formatName + "."
                );
            }
            return formatName;
        }

        private void write(T source, final ImageOutputStream outputStream) throws IOException {
            // if (StringUtils.isEmpty(outputFormat)) {
            //     throw new IllegalStateException("Output format has not been set.");
            // }
            Objects.requireNonNull(outputStream, "Could not open OutputStream.");
            BufferedImage srcImage;
            if (source instanceof BufferedImage) {
                srcImage = (BufferedImage) source;
            } else if (source instanceof File) {
                srcImage = ImageIO.read((File) source);
            } else if (source instanceof MultipartFile) {
                srcImage = ImageIO.read(((MultipartFile) source).getInputStream());
                // 将MultipartFile装换为InputStream
                source = (T) ((MultipartFile) source).getInputStream();
            } else if (source instanceof InputStream) {
                srcImage = ImageIO.read((InputStream) source);
            } else {
                throw new IllegalArgumentException("Unsupported ImageIO Type.");
            }
            String outputFormatName = this.outputFormat(source, outputFormat);
            // 原图宽
            int width = srcImage.getWidth();
            // 原图高
            int height = srcImage.getHeight();
            // 如果没有设置宽高和压缩比,则自动识别最佳压缩比
            Builder builder = this.identifyCompress(compressionRatio, width, height);
            double scaleWidth = builder.scaleWidth;
            double scaleHeight = builder.scaleHeight;
            double outputQuality = builder.outputQuality;
            if (Objects.equals(outputQuality, Double.NaN)) {
                throw new IllegalArgumentException("outputQuality is null.");
            }
            // 缩放后宽
            int sclWidth = Objects.equals(Double.NaN, scaleWidth) ? width : (int) (width * scaleWidth);
            // 缩放后高
            int sclHeight = Objects.equals(Double.NaN, scaleHeight) ? height : (int) (height * scaleHeight);
//            Image from = srcImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
            // 输出BufferedImage流
            long startTime = System.currentTimeMillis();
            BufferedImage destImage =
                    new BufferedImage(sclWidth, sclHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = destImage.createGraphics();
            // 消除锯齿
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//            g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_DEFAULT);
            g.addRenderingHints(new HashMap<>());
            g.drawImage(srcImage, 0, 0, sclWidth, sclHeight, null);
            // 压缩后增加一点点锐化,如不需要的,以下4行代码可以干掉
            // 拉普拉斯边缘锐化
//            startTime = System.currentTimeMillis();
//            BufferedImage imageSharpen = ImageSharpen.lapLaceSharpDeal(destImage);
//            //设置为透明覆盖
//            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.2f));
//            //在背景图片上添加锐化的边缘
//            g.drawImage(imageSharpen, 0, 0, imageSharpen.getWidth(), imageSharpen.getHeight(), null);
//            // 释放对象 透明度设置结束
//            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

            g.dispose();
            ImageWriter writer = null;
            ImageTypeSpecifier type =
                    ImageTypeSpecifier.createFromRenderedImage(destImage);
            // formatName不生效, 所以统一使用jpg
//            Iterator iterIO = ImageIO.getImageWriters(type, outputFormatName);
            Iterator iterIO = ImageIO.getImageWriters(type, "jpg");
            if (iterIO.hasNext()) {
                writer = (ImageWriter) iterIO.next();
            }
            if (writer == null) {
                throw new IllegalArgumentException("ImageWriter is null.");
            }

            IIOImage iioImage = new IIOImage(destImage, null, null);
            ImageWriteParam param = writer.getDefaultWriteParam();
            if (param.canWriteCompressed() && !outputFormatName.equalsIgnoreCase("bmp")) {
                param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                param.setCompressionQuality((float) outputQuality);    //这里可以指定压缩的程度 0-1.0
            } else {
//                param.setCompressionQuality(0.0f);
            }

//            ImageOutputStream outputStream = ImageIO.createImageOutputStream(os);

//            if (outputStream == null) {
//                throw new IOException("Could not open OutputStream.");
//            }
            writer.setOutput(outputStream);
            writer.write(null, iioImage, param);
            writer.dispose();
            outputStream.close();
        }

        public ByteArrayInputStream asByteArray() throws IOException {
            Iterator<T> iter = sources.iterator();
            T source = iter.next();
            if (iter.hasNext()) {
                throw new IllegalArgumentException("Cannot create one thumbnail from multiple original images.");
            }
            // 将缓存中的图片按照指定的配置输出到字节数组中
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            write(source, ImageIO.createImageOutputStream(byteArrayOutputStream));
            // 从字节数组中读取图片
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
//            InputStream inputStream = new ByteArrayInputStream(byteArrayInputStream);
//            MultipartFile file = new MockMultipartFile(ContentType.APPLICATION_OCTET_STREAM.toString(), byteArrayInputStream);
            return byteArrayInputStream;
        }

        public void toFile(final File outFile) throws IOException {
            Iterator<T> iter = sources.iterator();
            T source = iter.next();
            if (iter.hasNext()) {
                throw new IllegalArgumentException("Cannot create one thumbnail from multiple original images.");
            }
            write(source, ImageIO.createImageOutputStream(outFile));
        }

        private void toFiles(Iterable<File> iterable) throws IOException {
            Iterator<File> filenameIter = iterable.iterator();
            for (T source : sources) {
                if (!filenameIter.hasNext()) {
                    throw new IndexOutOfBoundsException(
                            "Not enough file names provided by iterator."
                    );
                }
                write(source, ImageIO.createImageOutputStream(filenameIter.next()));
            }
        }

        public void toFiles(File destinationDir, String namePrefix) throws IOException {
            if (destinationDir == null && namePrefix == null) {
                throw new NullPointerException("destinationDir and rename is null.");
            }
            if (destinationDir != null && !destinationDir.isDirectory()) {
                destinationDir.mkdir();
//                throw new IllegalArgumentException("Given destination is not a directory.");
            }

            if (destinationDir != null && !destinationDir.isDirectory()) {
                throw new IllegalArgumentException("Given destination is not a directory.");
            }
            long startTime = System.currentTimeMillis();
            Builder<T> builder = outputFormat(outputFormat);
            StreamSupport.stream(sources.spliterator(), true).forEach(source -> {
                if (!(source instanceof File)) {
                    throw new IllegalStateException("Cannot create thumbnails to files if original images are not from files.");
                }
                File f = (File) source;
                File actualDestDir = destinationDir == null ? f.getParentFile() : destinationDir;
                String name = StringUtils.isEmpty(namePrefix) ? f.getName() : namePrefix + f.getName();
                if (!Objects.equals(orgForm, builder.outputFormat)) {
                    name = name.substring(0, name.lastIndexOf(".")) + "." + outputFormat;
                }
                File destinationFile = new File(actualDestDir, name);
                try {
                    write((T) source, ImageIO.createImageOutputStream(destinationFile));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

        public void toOutputStream(final OutputStream outputStream) throws IOException {
            Iterator<T> iter = sources.iterator();
            T source = iter.next();
            if (iter.hasNext()) {
                throw new IllegalArgumentException("Cannot create one thumbnail from multiple original images.");
            }
            write(source, ImageIO.createImageOutputStream(outputStream));
        }

        public void toOutputStreams(Iterable<? extends OutputStream> iterable) throws IOException {
            Iterator<? extends OutputStream> filenameIter = iterable.iterator();
            for (T source : sources) {
                if (!filenameIter.hasNext()) {
                    throw new IndexOutOfBoundsException(
                            "Not enough file names provided by iterator."
                    );
                }
                write(source, ImageIO.createImageOutputStream(filenameIter.next()));
            }
        }
    }
}

我这边使用方法步骤如下,单张图片

// bytes 图片字节数组, targetPath 保存路径
public static Boolean compressImage(byte [] bytes, String targetPath) {
        try {
            // 创建一个临时文件
            File tempFile = File.createTempFile("temp", ".jpg");
            FileOutputStream fos = new FileOutputStream(tempFile);
            fos.write(bytes);
            // 获得水印后的文件大小
            long fileSize = tempFile.length() / 1024;
            // 判断文件大小,决定压缩倍率,开始压缩图片
            if (fileSize < 80) { // 图片小于80k,直接上传
                // 开始上传
                // 这里自己写.......................
                return
            }
            // 默认压缩率0.9,压缩率越低,压缩强度越高
            double quality = 0.9;
            if (fileSize > 150 && fileSize <= 300) { // 150 以上,300k以内的图片,压缩0.8
                quality = 0.8;
            } else if (fileSize > 300 && fileSize <= 500) { // 300 以上,500k以内的图片,压缩0.6
                quality = 0.6;
            } else if (fileSize > 500 && fileSize <= 1024) { // 500 以上,2M以内的图片,压缩0.4
                quality = 0.4;
            } else if (fileSize > 1024 && fileSize <= 5012) { // 1M以上,5M以内的图片,压缩0.3
                quality = 0.3;
            } else if (fileSize > 5012) { // 5M以上的图片,压缩0.2
                quality = 0.2;
            }
            double scale = 1;
            if (fileSize > 150) {
                // 获取图片宽高决定缩放比例
                BufferedImage imageIo = ImageIO.read(new ByteArrayInputStream(bytes));
                int width = imageIo.getWidth();
                int height = imageIo.getHeight();
                if (width > 1000 && width <= 2000 && height > 1000 && height <= 2000) {
                    scale = 0.8;
                } else if (width > 2000 && height > 2000) {
                    scale = 0.5;
                }
                if (width > 3000 && height > 3000) {
                    scale = 0.3;
                }
            }
            File saveFile = new File(targetPath);
            // 开始压缩
            ThumbnailUtil.of(tempFile).scale(scale).outputQuality(quality).toFile(saveFile);

            // 如果压缩后的文件比压缩前还要大,则删除掉,直接存压缩前的
            Long saveFileSize = saveFile.length() / 1024;
            if (saveFileSize > fileSize) {
                saveFile.delete();
                // 开始上传
                // 这里自己写...........................
                return
            }
            // 删除临时文件
            tempFile.delete();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

码字不易,于你有利,勿忘点赞  

孤村落日残霞,轻烟老树寒鸦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值