Reputation: 9439
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):
Output image (pinkHeartOut.gif):
Update case 2
Input image (example.gif):
Output image (exampleOut.gif): The output's yellow totally disappears!
Upvotes: 4
Views: 3137
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
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
Reputation: 4254
I don't have java at the moment but I think you should play with ColorModel of BufferedImage.
Upvotes: -2