图片压缩优化

393 阅读3分钟

起因

项目的需求:需要将传入图片高清等比压缩,经过调查,最后选择了Thumbnailator。但它在等比压缩的时候需要获取原图片的宽高,原本是使用ImageIo.read()来获取的,然后传入大图片的时候提示了堆异常,虽然这种错误一般通过修改jvm的启动参数xmx xms就可以解决,但是我想有没有办法能够用更优雅的方式解决这个问题,而不是单单只增加堆内存。

解决过程

想法很简单,既然用ImageIO读取会异常,那我换另一种方式不就行了么,然后偶尔的情况下看到阿里的ossJdk是支持对图片进行一系列操作的,例如获取信息,加水印,缩放等等。嗯嗯发现的有点晚,而后在这些接口文档里面我看了熟悉的imageWidth和imageHeigh,没错这就是我想要的。因为项目是先上传原图在压缩进行ocr识别的,所以可以用这种。 下面附上代码:阿里的代码返回的竟然是ossObject,开始的时候着实让我费解了一会,之后才弄清楚了它是把返回信息房子里面response中的。

 public String gainImageInfo(String fileName) throws IOException {
        OSSClient ossClient = new OSSClient(businessConfig.getEndpoint(), businessConfig.getAccessKeyId(),
                businessConfig.getAccessKeySecret());
        String style = "image/info";
        GetObjectRequest request = new GetObjectRequest(businessConfig.getBucketName(), fileName);
        request.setProcess(style);
        InputStream inputStream = ossClient.getObject(request).getObjectContent();

        StringBuilder sb = new StringBuilder();
        String line;

        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        String str = sb.toString();
        log.info("数据:{}", str);
        // 关闭OSSClient。
        inputStream.close();
        ossClient.shutdown();
        return str;
    }

下面展示的是如何处理结果:

 String height = "ImageHeight";
 String width = "ImageWidth";
 JSON.parseObject(jsonObject.getString(height)).getInteger("value")
 JSON.parseObject(jsonObject.getString(width)).getInteger("value")

下面的是图片压缩的代码:

 /**
     * 图片压缩
     *
     * @param data        字节流
     * @param imageHeight 图片高
     * @param imageWidth  图片宽
     * @param maxSize     最大大小
     * @return byte[]
     * @throws IOException IO异常
     */
    public static byte[] commpressPicForScale(byte[] data, Integer imageHeight, Integer imageWidth, Integer maxSize) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            long srcFileSize = data.length;
            double accuracy = getAccuracy(srcFileSize / 1024);
            //获取图片信息
            int srcWidth = imageWidth;
            int srcHeight = imageHeight;

            //先转换成jpg
            Thumbnails.Builder builder = Thumbnails.of(new ByteArrayInputStream(data)).outputFormat("jpg");

            //宽高均小,指定原大小
            builder.size(srcWidth, srcHeight);

            // 写入到内存
            builder.toOutputStream(baos);
            // 递归压缩,直到目标文件大小小于desFileSize
            byte[] bytes = commpressPicCycle(baos.toByteArray(), maxSize, accuracy);
            // 输出到文件
            baos.close();

            return bytes;
        } catch (Exception e) {
           log.error("图片压缩错误!"+e.getMessage());
            return new byte[]{};
        } finally {
            baos.close();
        }
    }
    
     /**
     * 迭代压缩
     *
     * @param bytes       字节流
     * @param desFileSize 目标大小
     * @param accuracy    根据一定的换算公式计算出的最佳质量比
     * @return byte[]
     * @throws IOException io异常
     */
    private static byte[] commpressPicCycle(byte[] bytes, long desFileSize, double accuracy) throws IOException {
        long srcFileSizeJPG = bytes.length;
        // 2、判断大小,如果小于500kb,不压缩;如果大于等于500kb,压缩
        if (srcFileSizeJPG <= desFileSize * 1024) {
            return bytes;
        }
        // 计算宽高
        BufferedImage bim = ImageIO.read(new ByteArrayInputStream(bytes));
        int srcWdith = bim.getWidth();
        int srcHeigth = bim.getHeight();
        int desWidth = new BigDecimal(srcWdith).multiply(
                BigDecimal.valueOf(accuracy)).intValue();
        int desHeight = new BigDecimal(srcHeigth).multiply(
                BigDecimal.valueOf(accuracy)).intValue();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Thumbnails.of(new ByteArrayInputStream(bytes)).size(desWidth, desHeight).outputQuality(accuracy).toOutputStream(baos);
        return commpressPicCycle(baos.toByteArray(), desFileSize, accuracy);
    }

    /**
     * 自动调节精度(经验数值)
     *
     * @param size 源图片大小
     * @return 图片压缩质量比
     */
    private static double getAccuracy(long size) {
        double accuracy;
        if (size < 900) {
            accuracy = 0.88;
        } else if (size < 2047) {
            accuracy = 0.6;
        } else if (size < 3275) {
            accuracy = 0.55;
        } else {
            accuracy = 0.48;
        }
        return accuracy;
    }

虽然说处理方式不过优雅 但实际上满足了需求也稍微优化了点代码。最后补充个当天学到的合并List中一个类中相同的多个属性值的数据,并累加指定字段数据的写法:

 List<JidianActivityCouponDO> couponDOList = new ArrayList<>();
 couponDOS.parallelStream().collect(Collectors.groupingBy(
                    o -> (o.getRelationCouponId() + "" + o.getJidian()))).forEach(
                    (id, transfer) -> transfer.stream().reduce((a, b) -> {
                        JidianActivityCouponDO couponDO = new JidianActivityCouponDO();
                        couponDO.setRelationCouponId(a.getRelationCouponId());
                        couponDO.setJidian(a.getJidian());
                        couponDO.setId(a.getId());
                        couponDO.setCouponName(a.getCouponName());
                        couponDO.setCouponQuantity(a.getCouponQuantity());
                        couponDO.setCouponConvertQuantity(a.getCouponConvertQuantity() + b.getCouponConvertQuantity());
                        couponDO.setCouponUsedQuantity(a.getCouponUsedQuantity() + b.getCouponUsedQuantity());
                        return couponDO;
                    }).ifPresent(couponDOList::add)
            );
        }

愉快收工