emeraldhieu
emeraldhieu

Reputation: 9439

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.

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. 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):

enter image description here

Output image (pinkHeartOut.gif):

enter image description here

Update case 2

Input image (example.gif):

enter image description here

Output image (exampleOut.gif): The output's yellow totally disappears!

enter image description here

Upvotes: 4

Views: 3137

Answers (3)

Harald K
Harald K

Reputation: 27094

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. 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). 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. This type is used for images that have 1-4 bits per pixel, and multiple pixels "packed" into one byte. 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).

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):

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

Upvotes: 5

TanLingxiao
TanLingxiao

Reputation: 412

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

Upvotes: -2

Boris Ivanov
Boris Ivanov

Reputation: 4254

I don't have java at the moment but I think you should play with ColorModel of BufferedImage.

ColorModel

Upvotes: -2

Related Questions