33614

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

Question:

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

<a href="https://i.stack.imgur.com/Zvwuq.gif" rel="nofollow"><img alt="enter image description here" class="b-lazy" data-src="https://i.stack.imgur.com/Zvwuq.gif" data-original="https://i.stack.imgur.com/Zvwuq.gif" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

Output image (pinkHeartOut.gif):

<a href="https://i.stack.imgur.com/ItpiZ.gif" rel="nofollow"><img alt="enter image description here" class="b-lazy" data-src="https://i.stack.imgur.com/ItpiZ.gif" data-original="https://i.stack.imgur.com/ItpiZ.gif" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

<strong>Update case 2</strong>

Input image (example.gif):

<a href="https://i.stack.imgur.com/slEVb.gif" rel="nofollow"><img alt="enter image description here" class="b-lazy" data-src="https://i.stack.imgur.com/slEVb.gif" data-original="https://i.stack.imgur.com/slEVb.gif" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

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

<a href="https://i.stack.imgur.com/q7nub.gif" rel="nofollow"><img alt="enter image description here" class="b-lazy" data-src="https://i.stack.imgur.com/q7nub.gif" data-original="https://i.stack.imgur.com/q7nub.gif" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

Answer1:

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 <em>fixed, default palette</em> (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 <em>fixed, default 2 color black/white palette</em> (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; } <hr />

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

Answer2:

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

<a href="https://docs.oracle.com/javase/6/docs/api/java/awt/image/ColorModel.html" rel="nofollow">ColorModel</a>

Answer3:

I think this is a best way. <a href="https://github.com/videlalvaro/gifsockets/tree/master/src/java" rel="nofollow">detail</a>

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

Recommend

  • How can I flip the result of WebGLRenderingContext.readPixels()?
  • GIF image becomes wrong after ImageIO read() and write() operations
  • JPanel repaint from another class
  • Mouse event not being triggered on html5 canvas
  • FireFox : image base64 data using canvas object not working
  • Resizing images using createjs / easeljs
  • SWT - get actual screen position of an image
  • Image size doesn't match the canvas size
  • Java last added button allows agent.move, rest dont
  • How to get usable canvas from Mapbox GL JS
  • How can I use layers on WebGL?
  • Looking for details on the PixelOffsetMode Enumeration in .Net, WinForms
  • Android SurfaceView: Show Video after Images
  • How to draw the contour of a union of Path
  • How to Format XAxis values correctly in Line Chart in aChartEngine in android
  • Get byte[] from
  • IndexSizeError on drawImage on IE and Edge
  • Colour resource ID returning wrong value
  • How to move move a rectangle in JFrame using KeyListener?
  • write text on image and show it to a imageview
  • Why does it draw lines in the wrong place?
  • jQuery: add elements until a particular height is reached
  • Converting a WriteableBitmap image ToArray in UWP
  • Setting up SourceTree to merge unity3d scenes with UnityYAMLMerge
  • Reading JSON from a file using C++ REST SDK (Casablanca)
  • Why value captured by reference in lambda is broken? [duplicate]
  • Email format validation in mvc3 view
  • Retrieving value from sql ExecuteScalar()
  • C# - Is there a limit to the size of an httpWebRequest stream?
  • How to make a tree having multiple type of nodes and each node can have multiple child nodes in java
  • How to add date and time under each post in guestbook in google app engine
  • How to convert from System.Drawing.Color to Excel.ColorFormat in C#? Change comment color
  • javascript inside java/jsp code
  • Properly structure and highlight a GtkPopoverMenu using PyGObject
  • Matrix multiplication with MKL
  • Linker errors when using intrinsic function via function pointer
  • Windows forms listbox.selecteditem displaying “System.Data.DataRowView” instead of actual value
  • coudnt use logback because of log4j
  • LevelDB C iterator
  • How can i traverse a binary tree from right to left in java?