解决Java对图片进行裁剪后图片偏色现象

问题描述

使用Java代码下载图片,并对图片进行裁减后,图片出现偏色的现象。

图片展示

原图:
裁剪前的原图
裁剪后得到的图片
裁剪后的图片

问题代码

这里隐去公司信息,自己写了个demo,问题代码如下:

@Slf4j
public class FileUtil {
    public static void main(String[] args) {
        // url
        String[] urls = new String[]{
                "http://test:18181/g1/M01/00/01/rBQZSl5YrLKAE0xcABCU9yL_rvI049.jpg",
                "http://test:18181/g1/M01/00/01/rBQZSl5YvVWAD80KAAyrNKu2dbM553.jpg",
                "http://test:18181/g1/M00/00/01/rBQZSl5Xnf-AMoS6AADcVze2Feg316.jpg",
                "http://test:18181/g1/M01/00/00/rBQZSl5VDeiAWTzTAAYDKkQlXwM345.jpg"
        };
        Arrays.stream(urls)
                .forEach(url -> {
                    // 获取图片文件名
                    String picName = url.substring(url.lastIndexOf("/") + 1);
                    // 下载图片
                    byte[] imageData = downloadImage(url);

                    // 这里对图片进行裁减,并转码获得其base64串
                    InputStream inputStream = new ByteArrayInputStream(imageData);
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    byte[] transformImageData = null;
                    try {
                        BufferedImage bufferedImage = ImageIO.read(inputStream);
                        int width = bufferedImage.getWidth();
                        int height = bufferedImage.getHeight();
                        int leftX = width / 2;
                        int leftY = height / 2;
                        // 进行图片裁减,这里裁减的坐标不是实际裁剪坐标,是随便写的
                        BufferedImage cutImage = bufferedImage.getSubimage(
                                leftX,
                                leftY,
                                width,
                                height
                        );
                        // 将图片转为ByteArrayOutputStream ,图片格式全部转为jpg
                        ImageIO.write(cutImage, "jpg", outputStream);
                        transformImageData = outputStream.toByteArray();
                        String base64 = Base64.getEncoder().encodeToString(transformImageData);
                        log.info("base64={}", base64);
                    } catch (IOException e) {
                        System.out.println(e.getMessage());
                    }
                });
    }
}

问题分析

在网上找了很多文章,发现之前也有网友遇到类似的情况,但文章没有给出具体方案。后来就只能按代码功能一行一行地分析,最后发现对图片进行裁剪的代码都没有问题,问题就出现在ImageIO.write()这个方法的调用上,下图中的红色框部分。
导致问题的代码
后来测试发现

  1. 如果原图格式为jpeg,gif,bmp,强转成jpg,再将图片base64串转换成图片,图片不会出现偏色的现象
  2. 如果原图格式为png,这里强转成jpg后,再将图片base64串转换成图片,则图片会出现偏色的现象

测试的时候借助了网上的base64图片在线转换工具,感觉很好用,感谢。

解决问题

为了解决上面的问题,就需要根据图片内容来获取图片的格式。

最初的时候考虑根据图片名来获取图片的格式,后来发现这样只可以解决部分图片的问题,假如图片文件名后缀与图片实际的格式不相同,则还是会出现色差。

最后,只能考虑通过图片本身的字节码或图片流来获取图片格式:

  1. 通过图片流获取图片格式

    这种方法在试验的过程中,发现返回的图片格式与图片名的后缀相同,就舍弃了。不知道是巧合,还是图片流的图片类型获取方法就是读取的文件名后缀,当时没有深究,所以不太确定,需要去看源码。

  2. 通过图片字节码获取

    实验发现可行,参考文章Java 读取文件 文件头字节码信息,判断文件类型

实现代码

  • 测试的主类
package test;

import com.google.common.io.Files;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;

import static test.HttpUtil.downloadImage;

/**
 * @author zhang
 * @date 2020/2/28 17:17
 */
@Slf4j
public class FileUtil {

    public static void main(String[] args) {
        // url
        String[] urls = new String[]{
                "http://test:18181/g1/M01/00/01/rBQZSl5YrLKAE0xcABCU9yL_rvI049.jpg",
                "http://test:18181/g1/M01/00/01/rBQZSl5YvVWAD80KAAyrNKu2dbM553.jpg",
                "http://test:18181/g1/M00/00/01/rBQZSl5Xnf-AMoS6AADcVze2Feg316.jpg",
                "http://test:18181/g1/M01/00/00/rBQZSl5VDeiAWTzTAAYDKkQlXwM345.jpg"
        };
        Arrays.stream(urls)
                .forEach(url -> {
                    String picName = url.substring(url.lastIndexOf("/") + 1);
                    byte[] imageData = downloadImage(url);
                    String picFormat = getFormatName(picName, imageData);
                    System.out.println("fileName: " + picName + ", fileFormat: " + picFormat);
                });
    }


    /**
     * 根据图片名来获取图片格式
     * @param imageName 图片名
     * @return 图片格式
     */
    private static String getFormatName(String imageName) {
        String extension = Files.getFileExtension(imageName);
        if (StringUtils.isBlank(extension)) {
            return FileFormatEnum.JPG.getValue();
        }

        for (FileFormatEnum fileFormatEnum : FileFormatEnum.values()) {
            if (StringUtils.equalsIgnoreCase(fileFormatEnum.getValue(), extension)) {
                return fileFormatEnum.getValue();
            }
        }
        return FileFormatEnum.UNKNOWN.getValue();
    }

    /**
     * 获取图片格式
     * @param imageName 图片名
     * @param imageBytes 图片字节码
     * @return 图片格式
     */
    private static String getFormatName(String imageName, byte[] imageBytes) {
        // 根据图片字节码读取图片类型
        FileFormatEnum formatName = getImageFormat(imageBytes);
        if (formatName != FileFormatEnum.UNKNOWN) {
            return formatName.getValue();
        }

        // 如果通过字节码没有取到图片类型,则根据文件名来判断
        return getFormatName(imageName);
    }

    /**
     * 读取图片格式
     * @param imageBytes 图片字节码数组
     * @return 图片格式
     */
    private static FileFormatEnum getImageFormat(byte[] imageBytes) {
        // 读取文件的前几个字节来判断图片格式
        byte[] b = Arrays.copyOf(imageBytes, 4);
        String type = bytesToHexString(b).toUpperCase();
        for (FileFormatEnum FileFormatEnum : FileFormatEnum.values()) {
            if (type.contains(FileFormatEnum.getCode())) {
                return FileFormatEnum;
            }
        }
        return FileFormatEnum.UNKNOWN;
    }


    /**
     * 将要读取文件头信息的文件的byte数组转换成string类型表示
     * @param src 要读取文件头信息的文件的byte数组
     * @return 图片类型字符串
     */
    private static String bytesToHexString(byte[] src){
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            // 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }
}

输出为
图片格式输出
从图可以看出,不管下载的图片文件名后缀是什么,通过字节码都能读到其实际的格式。

再将修改应用到最初存在问题的代码中,裁剪后的图片就没有偏色的现象了。
修改代码后剪裁的图片
修改后的部分代码如图所示:
修改后部分代码

  • 文件下载工具类
package test;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author zhang
 * @date 2020/2/28 17:11
 */
@Slf4j
public class HttpUtil {
    public static byte[] downloadImage(String url) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        byte[] dataBuffer = null;
        CloseableHttpResponse response = null;

        try {
            response = httpclient.execute(httpGet);
            int status = response.getStatusLine().getStatusCode();
            if (status == 200) {
                HttpEntity entity = response.getEntity();
                InputStream input = entity.getContent();
                ByteArrayOutputStream output = new ByteArrayOutputStream();
                IOUtils.copy(input, output);
                output.flush();
                input.close();
                dataBuffer = output.toByteArray();
                output.close();
                EntityUtils.consume(entity);
            }
        } catch (Exception var17) {
            log.warn("download image failed|url:{}", url, var17);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException var16) {
                    log.warn("close io exception failed", var16);
                }
            }

        }

        return dataBuffer;
    }
}

  • 文件名枚举类
    图片只使用了前几个,但这里为了以后方便,所以把知道的文件格式都列出来了。
package test;

/**
 * @author zhang
 * @date 2020/2/28 16:43
 */
public enum FileFormatEnum {
    /**
     *  文件格式枚举
     */
    JPG("FFD8FF", "jpg"),
//    JPEG("FFD8FFE0", "jpeg"),
    PNG("89504E47", "png"),
    GIF("47494638", "gif"),
    BMP("424D", "bmp"),
    
    TIF("49492A00", "tif"),
    DWG("41433130", "dwg"),
    PSD("38425053", "psd"),
    RTF("7B5C727466", "rtf"),
    XML("3C3F786D6C", "xml"),
    HTML("68746D6C3E", "html"),
    EML("44656C69766572792D646174653A", "eml"),
    /**
     *  word or xls
     */
    DOC("D0CF11E0", "doc"),
    MDB("5374616E64617264204A", "mdb"),
    PS("252150532D41646F6265", "ps"),
    PDF("255044462D312E", "pdf"),
    /**
     * word  or  xlsx
     */
    DOCX("504B0304", "docx"),
    RAR("52617221", "rar"),
    WAV("57415645", "wav"),
    AVI("41564920", "avi"),
    RM("2E524D46", "rm"),
    MPG("000001B", "mpg"),
    MOV("6D6F6F76", "mov"),
    ASF("3026B2758E66CF11", "asf"),
    MID("4D546864", "mid"),
    GZ("1F8B08", "gz"),
    EXE_DLL("4D5A9000", "exe/dll"),
    TXT("75736167", "txt"),

    UNKNOWN("000000", "unknown")
 ;

    private String code;
    private String value;

    FileFormatEnum(String code ,String value){
        this.code = code;
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    public String getCode() {
        return this.code;
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Amy_Min

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值