ImageIO read() 和 write() 操作后 GIF 图像变得错误
GIF image becomes wrong after ImageIO read() and write() operations
我有这个代码。它只是读取一个 GIF 文件,用背景重新绘制它并输出到一个新的 GIF 文件。
问题是结果文件变得奇怪了。我不知道为什么它会变得质量差。 JPG 文件不会出现此问题。如何解决?
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageTest {
public static void main(String[] args) {
f();
}
private static final String EXTENSION = "gif";
private static final String FILENAME = "pinkHeart";
private static final String PATH = "/Users/hieugioi/Downloads/";
public static void f() {
File file = new File(PATH + FILENAME + "." + EXTENSION);
try {
final BufferedImage originalImage = ImageIO.read(file);
int imageType = getImageType(originalImage);
final BufferedImage buff = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), imageType);
final Graphics2D g = buff.createGraphics();
Color backgroundColor = Color.GRAY;
g.setColor(backgroundColor);
g.fill(new Rectangle(0, 0, buff.getWidth(), buff.getHeight()));
g.drawImage(originalImage, null, 0, 0);
File out = new File(PATH + FILENAME + "Out." + EXTENSION);
ImageIO.write(buff, EXTENSION, out);
} catch (IOException e) {
e.printStackTrace();
}
}
public static int getImageType(BufferedImage img) {
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_CUSTOM) {
if (img.getAlphaRaster() != null) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
} else {
imageType = BufferedImage.TYPE_INT_RGB;
}
} else if (imageType == BufferedImage.TYPE_BYTE_INDEXED && img.getColorModel().hasAlpha()) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
}
return imageType;
}
}
输入图像(pinkHeart.gif):
输出图像(pinkHeartOut.gif):
更新案例 2
输入图像(example.gif):
输出图像(exampleOut.gif):输出的黄色完全消失!
目前我没有 java,但我认为你应该使用 BufferedImage 的 ColorModel。
我认为这是最好的方法。 detail
BufferedImage src1 = ImageIO.read(new File("test.jpg"));
BufferedImage src2 = ImageIO.read(new File("W.gif"));
AnimatedGifEncoder e = new AnimatedGifEncoder();
e.setRepeat(0);
e.start("laoma.gif");
e.setDelay(300); // 1 frame per sec
e.addFrame(src1);
e.setDelay(100);
e.addFrame(src2);
e.setDelay(100);
e.finish();
这里有两个不同的问题。
首先是假设您的输入图像具有透明度。据我所知,他们没有。因此,背景不会变成灰色,而是在两种情况下都保持纯白色。这没什么问题,但也许不是你 intended/expected.
另一个("real" 问题)是 getImageType(..)
的代码没有没有 alpha 的 BufferedImage.TYPE_BYTE_INDEXED
的特殊分支。因此,图像类型将按原样返回。当使用 BufferedImage.TYPE_BYTE_INDEXED
类型创建 BufferedImage
时,它将有一个带有 固定默认调色板 的颜色模型(事实上,它是很好的旧 256颜色 "web safe" 调色板)。您原版中的粉红色与此调色板中的粉红色不完全匹配,因此使用粉红色和白色进行抖动处理。
第二个输入图像的 "issue" 根本不是 TYPE_BYTE_INDEXED
,而是 TYPE_BYTE_BINARY
。此类型用于每个像素有 1-4 位且多个像素 "packed" 为一个字节的图像。如上所述,当使用 BufferedImage.TYPE_BYTE_BINARY
类型创建 BufferedImage
时,它将有一个带有 固定的颜色模型,默认 2 颜色 black/white 调色板 (这就是黄色消失的原因)。
通过在 getImageType(..)
方法中为上述类型添加分支,而不是 returns TYPE_INT_RGB
,我得到与您原来相同的输出(这是我所期望的,只要因为你的图片没有透明背景):
public static int getImageType(BufferedImage img) {
int imageType = img.getType();
switch (imageType) {
case BufferedImage.TYPE_CUSTOM:
if (img.getAlphaRaster() != null) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
}
else {
imageType = BufferedImage.TYPE_INT_RGB;
}
break;
case BufferedImage.TYPE_BYTE_BINARY:
// Handle both BYTE_BINARY (1-4 bit/pixel) and BYTE_INDEXED (8 bit/pixel)
case BufferedImage.TYPE_BYTE_INDEXED:
if (img.getColorModel().hasAlpha()) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
}
else {
// Handle non-alpha variant
imageType = BufferedImage.TYPE_INT_RGB;
}
break;
}
return imageType;
}
PS:这是一种替代方法,它避免了完全创建原始图像副本的问题,而且速度更快,而且还节省了内存。它应该与上面的代码完全相同:
public class ImageTest2 {
public static void main(String[] args) throws IOException {
f(new File(args[0]));
}
static void f(File file) throws IOException {
BufferedImage image = ImageIO.read(file);
// TODO: Test if image has transparency before doing anything else,
// otherwise just copy the original as-is, for even better performance
Graphics2D g = image.createGraphics();
try {
// Here's the trick, with DstOver we'll paint "behind" the original image
g.setComposite(AlphaComposite.DstOver);
g.setColor(Color.GRAY);
g.fill(new Rectangle(0, 0, image.getWidth(), image.getHeight()));
}
finally {
g.dispose();
}
File out = new File(file.getParent() + File.separator + file.getName().replace('.', '_') + "_out.gif");
ImageIO.write(image, "GIF", out);
}
}
我有这个代码。它只是读取一个 GIF 文件,用背景重新绘制它并输出到一个新的 GIF 文件。
问题是结果文件变得奇怪了。我不知道为什么它会变得质量差。 JPG 文件不会出现此问题。如何解决?
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageTest {
public static void main(String[] args) {
f();
}
private static final String EXTENSION = "gif";
private static final String FILENAME = "pinkHeart";
private static final String PATH = "/Users/hieugioi/Downloads/";
public static void f() {
File file = new File(PATH + FILENAME + "." + EXTENSION);
try {
final BufferedImage originalImage = ImageIO.read(file);
int imageType = getImageType(originalImage);
final BufferedImage buff = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), imageType);
final Graphics2D g = buff.createGraphics();
Color backgroundColor = Color.GRAY;
g.setColor(backgroundColor);
g.fill(new Rectangle(0, 0, buff.getWidth(), buff.getHeight()));
g.drawImage(originalImage, null, 0, 0);
File out = new File(PATH + FILENAME + "Out." + EXTENSION);
ImageIO.write(buff, EXTENSION, out);
} catch (IOException e) {
e.printStackTrace();
}
}
public static int getImageType(BufferedImage img) {
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_CUSTOM) {
if (img.getAlphaRaster() != null) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
} else {
imageType = BufferedImage.TYPE_INT_RGB;
}
} else if (imageType == BufferedImage.TYPE_BYTE_INDEXED && img.getColorModel().hasAlpha()) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
}
return imageType;
}
}
输入图像(pinkHeart.gif):
输出图像(pinkHeartOut.gif):
更新案例 2
输入图像(example.gif):
输出图像(exampleOut.gif):输出的黄色完全消失!
目前我没有 java,但我认为你应该使用 BufferedImage 的 ColorModel。
我认为这是最好的方法。 detail
BufferedImage src1 = ImageIO.read(new File("test.jpg"));
BufferedImage src2 = ImageIO.read(new File("W.gif"));
AnimatedGifEncoder e = new AnimatedGifEncoder();
e.setRepeat(0);
e.start("laoma.gif");
e.setDelay(300); // 1 frame per sec
e.addFrame(src1);
e.setDelay(100);
e.addFrame(src2);
e.setDelay(100);
e.finish();
这里有两个不同的问题。
首先是假设您的输入图像具有透明度。据我所知,他们没有。因此,背景不会变成灰色,而是在两种情况下都保持纯白色。这没什么问题,但也许不是你 intended/expected.
另一个("real" 问题)是 getImageType(..)
的代码没有没有 alpha 的 BufferedImage.TYPE_BYTE_INDEXED
的特殊分支。因此,图像类型将按原样返回。当使用 BufferedImage.TYPE_BYTE_INDEXED
类型创建 BufferedImage
时,它将有一个带有 固定默认调色板 的颜色模型(事实上,它是很好的旧 256颜色 "web safe" 调色板)。您原版中的粉红色与此调色板中的粉红色不完全匹配,因此使用粉红色和白色进行抖动处理。
第二个输入图像的 "issue" 根本不是 TYPE_BYTE_INDEXED
,而是 TYPE_BYTE_BINARY
。此类型用于每个像素有 1-4 位且多个像素 "packed" 为一个字节的图像。如上所述,当使用 BufferedImage.TYPE_BYTE_BINARY
类型创建 BufferedImage
时,它将有一个带有 固定的颜色模型,默认 2 颜色 black/white 调色板 (这就是黄色消失的原因)。
通过在 getImageType(..)
方法中为上述类型添加分支,而不是 returns TYPE_INT_RGB
,我得到与您原来相同的输出(这是我所期望的,只要因为你的图片没有透明背景):
public static int getImageType(BufferedImage img) {
int imageType = img.getType();
switch (imageType) {
case BufferedImage.TYPE_CUSTOM:
if (img.getAlphaRaster() != null) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
}
else {
imageType = BufferedImage.TYPE_INT_RGB;
}
break;
case BufferedImage.TYPE_BYTE_BINARY:
// Handle both BYTE_BINARY (1-4 bit/pixel) and BYTE_INDEXED (8 bit/pixel)
case BufferedImage.TYPE_BYTE_INDEXED:
if (img.getColorModel().hasAlpha()) {
imageType = BufferedImage.TYPE_INT_ARGB_PRE;
}
else {
// Handle non-alpha variant
imageType = BufferedImage.TYPE_INT_RGB;
}
break;
}
return imageType;
}
PS:这是一种替代方法,它避免了完全创建原始图像副本的问题,而且速度更快,而且还节省了内存。它应该与上面的代码完全相同:
public class ImageTest2 {
public static void main(String[] args) throws IOException {
f(new File(args[0]));
}
static void f(File file) throws IOException {
BufferedImage image = ImageIO.read(file);
// TODO: Test if image has transparency before doing anything else,
// otherwise just copy the original as-is, for even better performance
Graphics2D g = image.createGraphics();
try {
// Here's the trick, with DstOver we'll paint "behind" the original image
g.setComposite(AlphaComposite.DstOver);
g.setColor(Color.GRAY);
g.fill(new Rectangle(0, 0, image.getWidth(), image.getHeight()));
}
finally {
g.dispose();
}
File out = new File(file.getParent() + File.separator + file.getName().replace('.', '_') + "_out.gif");
ImageIO.write(image, "GIF", out);
}
}