【Java】将Base64格式的图片等比/非等比伸缩至目标尺寸代码实现

等比伸缩

需求

前端页面上传的图片是Base64字符串,需要根据目标尺寸进行伸缩,不能改变图片的比例

代码实现

使用图片处理工具:thumbnailator

引入Maven依赖:

        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.8</version>
        </dependency>

核心代码:

import net.coobird.thumbnailator.Thumbnails;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Base64;
import java.util.Objects;

public class Test {
    public static final String IMAGE_BASE64_PREFIX_REG = "data:image/[^;]+;base64,";
  	// 你的测试数据
    private static final String IMG_BASE64 = "";
    private static final String FILE_PATH = "/Users/xdf/Desktop/Temp/test.png";
    private static Integer targetHeight = 400;
    private static Integer targetWidth = 400;

    public static void main(String[] args) throws IOException {
        DecimalFormat df = new DecimalFormat("#.##");
        df.setRoundingMode(RoundingMode.FLOOR);
        String image = IMG_BASE64.replaceAll(IMAGE_BASE64_PREFIX_REG, "");
        BufferedImage bufferedImage = convertBase64ToImage(image);
        if (Objects.isNull(bufferedImage)) {
            System.err.println("covert base64 image error.");
            return;
        }
        int height = bufferedImage.getHeight();
        int width = bufferedImage.getWidth();
        // 计算缩放比例,目标尺寸/当前尺寸,大于0则需要放大(目标大),小于0则需要缩放(目标小)
        double heightScale = Double.parseDouble(df.format((double) targetHeight / height));
        double widthScale = Double.parseDouble(df.format((double) targetWidth / width));
        System.out.printf("[current height]: %d, [current width]: %d \n[target height]: %d, [target width]: %d\n", height, width, targetHeight, targetWidth);
        // 放大时,需要以目标较长的一边作为放大基准;缩小时,需要以当前尺寸较短的一边为基准(当目标尺寸大时,分子越大,差距越大,当目标尺寸小时,分母越小,差距越大,两种情况都会导致商更大)
        double scale = Math.max(heightScale, widthScale);
        System.out.printf("[heightScale]: %s, [widthScale]: %s, [final scale]: %s \n", heightScale, widthScale, scale);
        BufferedImage targetImage = Thumbnails.of(bufferedImage).scale(scale).asBufferedImage();
        ImageIO.write(targetImage, "png", new File(FILE_PATH));
    }

    public static BufferedImage convertBase64ToImage(String base64String) {
        try {
            // 解码Base64字符串为字节数组
            byte[] imageBytes = Base64.getDecoder().decode(base64String);
            // 创建ByteArrayInputStream对象
            ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes);
            // 将ByteArrayInputStream对象转换为BufferedImage对象
            BufferedImage bufferedImage = ImageIO.read(bis);
            // 关闭ByteArrayInputStream
            bis.close();
            // 返回BufferedImage对象
            return bufferedImage;
        } catch (Exception e) {
            System.err.println(e);
        }
        return null;
    }
}

这个方案是保证图片完整的情况下,尽量接近目标尺寸,如果要精确等于目标尺寸,还需要用到裁剪或者下面的非等比方法,做微调。

非等比

使用jdk自带的BufferedImage实现:

    public static String resizeBase64Image(final String originalBase64Image, final Integer targetWidth, final Integer targetHeight) throws Exception {
        // base64转为BufferedImage
        log.info("start to resize picture...");
        long start = System.currentTimeMillis();
        BufferedImage originalImage = convertBase64ToImage(originalBase64Image);
        assert originalImage != null;
        // 将图片伸缩至目标长宽(非等比)
        BufferedImage targetImage = resizeImage(originalImage, targetWidth, targetHeight);
        String targetBase64Image = convertImageToBase64(targetImage);
        log.info("resize image finished. [cost]: {} ms", System.currentTimeMillis() - start);
        return targetBase64Image;
    }

相关方法:

    /**
     * 通过BufferedImage图片流调整图片大小
     * 在创建新的 BufferedImage 实例时,将图像类型设置为 BufferedImage.TYPE_INT_RGB,它不支持透明度。原始图像的透明区域在目标图像中会变成黑色。
     * 如果想保留原始图像的透明区域,需要使用支持透明度的图像类型,比如 BufferedImage.TYPE_INT_ARGB。
     */
    public static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException {
        if (originalImage.getWidth() == targetWidth && originalImage.getHeight() == targetHeight) {
            return originalImage;
        }
        Image resultingImage = originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_AREA_AVERAGING);
        BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB);
        outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
        return outputImage;
    }

Base64转BuffedImage:

    public static BufferedImage convertBase64ToImage(String base64String) {
        try {
            // 解码Base64字符串为字节数组
            byte[] imageBytes = Base64.getDecoder().decode(base64String);
            // 创建ByteArrayInputStream对象
            ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes);
            // 将ByteArrayInputStream对象转换为BufferedImage对象
            BufferedImage bufferedImage = ImageIO.read(bis);
            // 关闭ByteArrayInputStream
            bis.close();
            // 返回BufferedImage对象
            return bufferedImage;
        } catch (Exception e) {
            log.info("convertBase64ToImage error: {}", e.getMessage());
        }

        return null;
    }

BuffedImage转Base64:

    public static String convertImageToBase64(BufferedImage image) {
        try {
            // 创建字节数组输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 将BufferedImage对象写入字节数组输出流
            ImageIO.write(image, "png", baos);
            // 关闭字节数组输出流
            baos.close();
            // 获取字节数组
            byte[] imageBytes = baos.toByteArray();
            // 返回Base64编码的字符串
            return Base64.getEncoder().encodeToString(imageBytes);
        } catch (Exception e) {
            log.info("convertImageToBase64 error: {}", e.getMessage());
        }
        return null;
    }