关于java:ImageIO read()和write()操作后GIF图像变得错误

GIF image becomes wrong after ImageIO read() and write() operations

我有这个代码。 它只是读取一个GIF文件,使用背景重绘该文件并输出到新的GIF文件。

问题是结果文件变得奇怪。 我不知道为什么它质量会变差。 JPG文件上不会发生此问题。 如何解决?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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):

enter image description here

输出图像(pinkHeartOut.gif):

enter image description here

更新案例2

输入图像(example.gif):

enter image description here

输出图像(exampleOut.gif):输出的黄色完全消失!

enter image description here


这里有两个独立的问题。

首先是假设您的输入图像具有透明度。据我所知,它们没有。因此,在两种情况下,背景都不会变为灰色,而是保持纯白色。这没有什么错,但也许不是您的预期/期望。

另一个("实际"问题)是getImageType(..)的代码没有BufferedImage.TYPE_BYTE_INDEXED没有特殊分支。因此,图像类型将按原样返回。并且,当使用BufferedImage.TYPE_BYTE_INDEXED类型创建BufferedImage时,它将具有带有固定的默认调色板的颜色模型(实际上,它是旧的256色"网络安全"调色板)。原始色中的粉红色与该调色板中的粉红色不完全匹配,因此使用粉红色和白色来抖动。

第二个输入图像的"问题"根本不是TYPE_BYTE_INDEXED,而是TYPE_BYTE_BINARY。此类型用于每个像素具有1-4位且多个像素"打包"成一个字节的图像。如上所述,用BufferedImage.TYPE_BYTE_BINARY类型创建BufferedImage时,它将具有一个带有固定的默认2色黑白调色板的颜色模型(这就是黄色消失的原因)。

通过在返回TYPE_INT_RGBgetImageType(..)方法中添加上述类型的分支,我将获得与原始图像相同的输出(只要图像没有透明背景,这就是我期望的结果):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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:这是一种替代方法,可避免完全创建原始图像副本的问题,并且速度更快,并且可以节省内存。它应该与您上面的代码意图完全相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
    }
}


我认为这是最好的方法。详情

1
2
3
4
5
6
7
8
9
10
11
    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();


我目前没有Java,但我认为您应该使用BufferedImage的ColorModel。

色彩模型