ImageIO.read红色调问题
什么是红色调问题
在写一个压缩图像的工具类的时候,发现该问题。原图如下,大小为3.8M
,格式为png
:
压缩之后,变成以下这样,大小为1.4M
,格式为jpeg
:
搜了下网上有的说是:有损压缩算法会牺牲图像的一些细节和信息以减小文件大小,从而导致图像质量的损失。
虽说是有损,但这是不是损的太厉害了,色调变化也太大了。真的是这样吗?
排查
在翻了好多的资料以后,终于搜到了一些靠谱的词:红色调
。这不就是我压缩后图像的色调吗?顺着一搜,找到这篇:
Toolkit.getDefaultToolkit().createImage() vs ImageIO.read()
大概理解了问题的所在,再深入扒一下,得到了以下内容:只影响一些特定类型的JPEG图像,它们有一种特殊的子采样方式,并且没有JFIF标记。当ImageIO读取这些图像时,它会错误地将YCbCr数据解释为RGB数据,从而导致颜色失真
。
那什么是YCbCr
数据呢?
YCbCr
YCbCr数据是一种用来表示颜色的模型
,它使用一个亮度分量(Y)和两个色差分量(Cb和Cr)来定义颜色数据
。它常用于数字图像和视频场景中。YCbCr这个术语经常和YUV混用,尽管它们在技术上是不同的。在YCbCr中,Y表示颜色的明亮度和浓度,而Cb和Cr则分别表示颜色的蓝色浓度偏移量和红色浓度偏移量
。YCbCr数据通常使用平面的内存布局来存储,这意味着每个颜色分量都单独存储在自己的连续平面中,总共有三个平面。YCbCr数据可以通过数学变换从RGB颜色空间转换而来,也可以反过来转换回RGB。
也就是说ImageIO.read()方法将YCbCr数据直接解释为了RGB数据才导致上面的结果
。
解决
知道了问题的原因,那么我直接换个能正确读取YCbCr数据的方法来不就好了。
将以下代码,其中inputImage为图像的File对象
BufferedImage image = ImageIO.read(inputImage);
替换为:
Image image1 = Toolkit.getDefaultToolkit().getImage(inputImage.getAbsolutePath());
// 等待图像加载完成
MediaTracker mediaTracker = new MediaTracker(new Component() {});
mediaTracker.addImage(image1, 0);
try {
mediaTracker.waitForAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
//转为BufferdImage对象
BufferedImage image = toBufferedImage(image1);
toBufferedImage
public static BufferedImage toBufferedImage(Image image) {
//设置一个同宽高的基本图像
BufferedImage bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
//空图像的画布
Graphics2D g = bimage.createGraphics();
//绘制图像
g.drawImage(image, 0, 0, null);
//释放资源
g.dispose();
return bimage;
}
修改后得到的压缩图片文件如下,大小为1.4M
:
至此大功告成。