简体   繁体   English

在ImageIO read()和write()操作之后,GIF图像会出错

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

I have this code. 我有这个代码。 It simply reads a GIF file, redraws it with background and output to a new GIF file. 它只是读取一个GIF文件,用背景重绘它并输出到一个新的GIF文件。

The problem is the result file becomes strange. 问题是结果文件变得奇怪。 I have no idea why it becomes poor quality. 我不知道它为什么会变质。 The problem doesn't happen on JPG files. JPG文件不会发生此问题。 How to fix it? 怎么解决?

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;
    }
}

Input image (pinkHeart.gif): 输入图片(pinkHeart.gif):

在此输入图像描述

Output image (pinkHeartOut.gif): 输出图像(pinkHeartOut.gif):

在此输入图像描述

Update case 2 更新案例2

Input image (example.gif): 输入图像(example.gif):

在此输入图像描述

Output image (exampleOut.gif): The output's yellow totally disappears! 输出图像(exampleOut.gif):输出的黄色完全消失!

在此输入图像描述

There are two separate problems here. 这里有两个不同的问题。

The first is the assumption that your input images have transparency. 第一个假设您的输入图像具有透明度。 They do not, as far as I can see. 据我所知,它们没有。 Because of this, the background will not change to gray, but stay solid white in both cases. 因此,背景不会变为灰色,但在两种情况下都保持纯白色。 There's nothing wrong with this, but perhaps not what you intended/expected. 这没有什么不对,但也许不是你想要/期望的。

The other (the "real" problem) is that the code for getImageType(..) has no special branch for BufferedImage.TYPE_BYTE_INDEXED without alpha. 另一个(“真正的”问题)是getImageType(..)的代码没有针对没有alpha的BufferedImage.TYPE_BYTE_INDEXED特殊分支。 Because of this, the image type will just be returned as is. 因此,图像类型将按原样返回。 And when a BufferedImage is created with the BufferedImage.TYPE_BYTE_INDEXED type, it will have a color model with a fixed, default palette (in fact, it's the good old 256 color "web safe" palette). 当一个BufferedImage与创建BufferedImage.TYPE_BYTE_INDEXED类型,它将有一个固定的,默认调色板中的颜色模型(事实上,这是好老256色“网络安全”调色板)。 The pink color in your origininal does not exactly fit the pink in this palette, and is thus dithered using pink and white. 原点中的粉红色与此调色板中的粉红色不完全相符,因此使用粉红色和白色进行抖动。

The "issue" with your second input image, is that it is not TYPE_BYTE_INDEXED at all, it is TYPE_BYTE_BINARY . 第二个输入图像的“问题”是它根本不是TYPE_BYTE_INDEXED ,它是TYPE_BYTE_BINARY This type is used for images that have 1-4 bits per pixel, and multiple pixels "packed" into one byte. 此类型用于每像素具有1-4位的图像,并且多个像素“打包”到一个字节中。 As above, when a BufferedImage is created with the BufferedImage.TYPE_BYTE_BINARY type, it will have a color model with a fixed, default 2 color black/white palette (that's why the yellow disappear). 如上所述,当一个BufferedImage与创建BufferedImage.TYPE_BYTE_BINARY类型,它将有一个固定的,默认颜色2黑/白调色板中的颜色模型(这就是为什么黄色消失)。

By adding branches for the above types in the getImageType(..) method that returns TYPE_INT_RGB instead, I get the same output as your original (which is what I expect, as long as your image has no transparent background): 通过在返回TYPE_INT_RGBgetImageType(..)方法中为上述类型添加分支,我获得与原始相同的输出(这是我期望的,只要您的图像没有透明背景):

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: Here's an alternate approach that avoids the problem of creating a copy of the original image altogether, and is both faster and saves memory as a bonus. PS:这是一种替代方法,可以避免完全创建原始图像副本的问题,并且速度更快并且可以节省内存作为奖励。 It should do exactly the same as your code above intends: 它应该与上面的代码完全相同:

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);
    }
}

I don't have java at the moment but I think you should play with ColorModel of BufferedImage. 我目前没有java,但我认为你应该使用BufferedImage的ColorModel。

ColorModel 的ColorModel

I think this is a best way. 我认为这是一种最好的方式。 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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM