-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
25
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
Tested using Mac 15.4.1, Java v25
A DESCRIPTION OF THE PROBLEM :
When the transparent pixel index is non-zero and the frame disposal method changes from 2 to 1 (DISPOSAL_BACKGROUND_COLOR to DISPOSAL_DO_NOT_DISPOSE): the GifImageDecoder can mistakenly produce an opaque rectangle.
I have identified a potential fix for this issue and will create those PRs shortly when/if these tickets move to the bug database.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should pass.
ACTUAL -
The test failed with an error, because it identified an opaque pixel that should be transparent.
---------- BEGIN SOURCE ----------
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.IndexColorModel;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.concurrent.Semaphore;
public class GifEmptyBackgroundTest {
public static void main(String[] args) throws Exception {
URL srcURL = GifEmptyBackgroundTest.class.getResource("CC0-3D.gif");
BufferedImage[] frames = getFrames(srcURL, 31);
boolean pass = true;
if (new Color(frames[30].getRGB(20, 20), true).getAlpha() != 0) {
System.err.println("Sampling at (20,20) failed");
pass = false;
}
if (!pass)
throw new Error("See System.err for details");
}
private static BufferedImage[] getFrames(URL gifURL, int numberOfFrames) {
Image image = Toolkit.getDefaultToolkit().createImage(gifURL);
ArrayList<BufferedImage> returnValue = new ArrayList<>(numberOfFrames);
Semaphore semaphore = new Semaphore(1);
semaphore.acquireUninterruptibly();
image.getSource().startProduction(new ImageConsumer() {
BufferedImage bi;
int frameCtr = 0;
@Override
public void setDimensions(int width, int height) {
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
@Override
public void setProperties(Hashtable<?, ?> props) {}
@Override
public void setColorModel(ColorModel model) {}
@Override
public void setHints(int hintflags) {}
@Override
public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize) {
try {
final int yMax = y + h;
final int xMax = x + w;
IndexColorModel icm = (IndexColorModel) model;
int[] colorModelRGBs = new int[icm.getMapSize()];
icm.getRGBs(colorModelRGBs);
int[] argbRow = new int[bi.getWidth()];
for (int y_ = y; y_ < yMax; y_++) {
int i = y_ * scansize + off;
for (int x_ = x; x_ < xMax; x_++, i++) {
int pixel = pixels[i] & 0xff;
argbRow[x_ - x] = colorModelRGBs[pixel];
}
bi.getRaster().setDataElements(x, y_, w, 1, argbRow);
}
} catch (RuntimeException e) {
// we don't expect this to happen, but if something goes wrong nobody else
// will print our stacktrace for us:
e.printStackTrace();
throw e;
}
}
@Override
public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize) {}
@Override
public void imageComplete(int status) {
try {
frameCtr++;
BufferedImage copy = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = copy.createGraphics();
g.drawImage(bi, 0, 0, null);
g.dispose();
returnValue.add(copy);
if (frameCtr == numberOfFrames) {
semaphore.release();
// if we don't detach this consumer the producer will loop forever
image.getSource().removeConsumer(this);
image.flush();
}
} catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
});
semaphore.acquireUninterruptibly();
return returnValue.toArray(new BufferedImage[0]);
}
}
---------- END SOURCE ----------
Tested using Mac 15.4.1, Java v25
A DESCRIPTION OF THE PROBLEM :
When the transparent pixel index is non-zero and the frame disposal method changes from 2 to 1 (DISPOSAL_BACKGROUND_COLOR to DISPOSAL_DO_NOT_DISPOSE): the GifImageDecoder can mistakenly produce an opaque rectangle.
I have identified a potential fix for this issue and will create those PRs shortly when/if these tickets move to the bug database.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should pass.
ACTUAL -
The test failed with an error, because it identified an opaque pixel that should be transparent.
---------- BEGIN SOURCE ----------
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.IndexColorModel;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.concurrent.Semaphore;
public class GifEmptyBackgroundTest {
public static void main(String[] args) throws Exception {
URL srcURL = GifEmptyBackgroundTest.class.getResource("CC0-3D.gif");
BufferedImage[] frames = getFrames(srcURL, 31);
boolean pass = true;
if (new Color(frames[30].getRGB(20, 20), true).getAlpha() != 0) {
System.err.println("Sampling at (20,20) failed");
pass = false;
}
if (!pass)
throw new Error("See System.err for details");
}
private static BufferedImage[] getFrames(URL gifURL, int numberOfFrames) {
Image image = Toolkit.getDefaultToolkit().createImage(gifURL);
ArrayList<BufferedImage> returnValue = new ArrayList<>(numberOfFrames);
Semaphore semaphore = new Semaphore(1);
semaphore.acquireUninterruptibly();
image.getSource().startProduction(new ImageConsumer() {
BufferedImage bi;
int frameCtr = 0;
@Override
public void setDimensions(int width, int height) {
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
@Override
public void setProperties(Hashtable<?, ?> props) {}
@Override
public void setColorModel(ColorModel model) {}
@Override
public void setHints(int hintflags) {}
@Override
public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize) {
try {
final int yMax = y + h;
final int xMax = x + w;
IndexColorModel icm = (IndexColorModel) model;
int[] colorModelRGBs = new int[icm.getMapSize()];
icm.getRGBs(colorModelRGBs);
int[] argbRow = new int[bi.getWidth()];
for (int y_ = y; y_ < yMax; y_++) {
int i = y_ * scansize + off;
for (int x_ = x; x_ < xMax; x_++, i++) {
int pixel = pixels[i] & 0xff;
argbRow[x_ - x] = colorModelRGBs[pixel];
}
bi.getRaster().setDataElements(x, y_, w, 1, argbRow);
}
} catch (RuntimeException e) {
// we don't expect this to happen, but if something goes wrong nobody else
// will print our stacktrace for us:
e.printStackTrace();
throw e;
}
}
@Override
public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize) {}
@Override
public void imageComplete(int status) {
try {
frameCtr++;
BufferedImage copy = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = copy.createGraphics();
g.drawImage(bi, 0, 0, null);
g.dispose();
returnValue.add(copy);
if (frameCtr == numberOfFrames) {
semaphore.release();
// if we don't detach this consumer the producer will loop forever
image.getSource().removeConsumer(this);
image.flush();
}
} catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
});
semaphore.acquireUninterruptibly();
return returnValue.toArray(new BufferedImage[0]);
}
}
---------- END SOURCE ----------