-
Notifications
You must be signed in to change notification settings - Fork 5.9k
8356137: GifImageDecode can produce opaque image when disposal method changes #25044
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Merge openjdk/jdk into mickleness/jdk
Merge openjdk/jdk
Merge from openjdk/jdk
Updating mickleness/jdk from openjdk/jdk
updating to openjdk/jdk
Also adding accompanying unit test. I think there's been a mix-up regarding the sample image attached to the original ticket. This ticket should refer to `clyde.gif` (originally based on a data structure identified here: https://free-gifs.org/gif/CC0-3D ). There is another similar looking (but different) ticket I filed last week for which `leo.gif` is the appropriate test case file. (See incident report 9218362. As far as I can see that hasn't made its way to the bug database yet.) I had to email both gifs to someone on the triage team, and I think they got mixed up.
👋 Welcome back mickleness! A progress list of the required criteria for merging this PR into |
❗ This change is not yet ready to be integrated. |
@mickleness The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few questions.
Also, a copyright in GifImageDecoder.java
@@ -0,0 +1,143 @@ | |||
/* | |||
* Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: do you need 2002 here? Isn't it a new file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, this is removed
|
||
boolean pass = true; | ||
if (new Color(frames[3].getRGB(20, 20), true).getAlpha() != 0) { | ||
System.err.println("Sampling at (20,20) failed"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it would be cleaner if you jsut throw a RuntimeException("Sampling at (20,20) failed");
instead of the whole
System.err.println("Sampling at (20,20) failed");
pass = false;
}
if (!pass)
throw new Error("See System.err for details");
?
It should result in the same level of details but with better readability imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion; this is removed. (I often follow that pattern in case I try to add multiple criteria to pass/fail decisions.)
|
||
return returnValue.toArray(new BufferedImage[0]); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: could you please add a new line here for github? 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, this is updated
image.flush(); | ||
} | ||
} catch(Exception e) { | ||
e.printStackTrace(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to print out the stack trace here when you are throwing it below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is caught in the same place the other one is ( #25044 (comment) )
} 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need this print here? Runtime exception should print it out anyway to the system.error afaik
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need this print here?
Yes, because this eventually is caught here in GifImageDecoder.java:
try {
if (!readImage(totalframes == 0,
disposal_method,
delay)) {
return;
}
} catch (Exception e) {
if (verbose) {
e.printStackTrace();
}
return;
}
In this specific test file: I never expect an exception to be thrown, but one did come up when I was first drafting this test (because of my own error). It was hard to debug because it was unreported. I would prefer to leave these printStackTrace calls in, in case a developer someday makes a change and needs to see potential errors.
(Technically I could try to make verbose true
, but that's declared as a final variable and I don't want to modify GifImageDecoder.java just for this.)
* @test | ||
* @bug 8356137 | ||
* @summary This test verifies a non-zero transparent pixel in gifs works when | ||
* the disposal method changes from 2 to 1, and when the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Do you need the , and when the
? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is updated
This is in response to: openjdk#25044 (comment)
This is in response to: openjdk#25044 (comment)
This is in response to: openjdk#25044 (comment)
This is in response to: openjdk#25044 (comment)
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we a need a gif with only 2 frames having transpixel>0 and disposal method save to reproduce this scenario.
Also its better if we can actually create test gif file using ImageIO(There are examples in test/jdk/javax/imageio/plugins/gif/) programatically and use it instead of external GIF files for testing.
// If this is our first time updating it, and if zeroes | ||
// are NOT the transparent pixel: we need to replace it | ||
// with the appropriate transparent pixel. | ||
boolean replaceTransPixelsInSavedImage = save && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can move this check to the place where we actually create and initialize saved image.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this thread is moot having addressed #25044 (comment) .
@@ -424,6 +433,9 @@ private int sendPixels(int x, int y, int width, int height, | |||
} | |||
} | |||
runstart = -1; | |||
if (replaceTransPixelsInSavedImage) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are hitting this check for each transparent pixel for all frames with transparent pixel index > 0 and not initial frame until saved_image is not null. In the .gif present in this PR we are checking this condition for each transparent pixel of 2nd and 3rd frame. If GIF contains many frames and disposal method save comes in later part of animation we will hit this check many times.
I think its better to just initialize saved_image with proper transparent pixel index when it is getting created. Then we can completely remove state management of replaceTransPixelsInSavedImage
and this check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK; I think this thread is moot having addressed #25044 (comment) .
* Unit tests may further inspect this image to make sure certain | ||
* conditions are met. | ||
*/ | ||
public static BufferedImage run(URL srcURL) throws Throwable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can actually update this helper to check a specific frame is GIF and not run throw all the frame its better.
In this PR we just need the info of 4th frame.
Is this way of checking all frames useful in any other PR's which you have raised?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this way of checking all frames useful in any other PR's which you have raised?
No, I don't think so. (I liked the assurance that every frame was identical, but technically that is beyond the scope of each individual unit test.)
I changed this method to only inspect the last frame.
This made the GifComparison's main() class much less useful, so I removed it. So now the GifComparison class is more limited in scope to unit tests -- and it's no longer a generic tool to identify new problems.
URL srcURL = GifEmptyBackgroundTest.class.getResource("clyde.gif"); | ||
BufferedImage bi = GifComparison.run(srcURL); | ||
|
||
if (new Color(bi.getRGB(20, 20), true).getAlpha() != 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the product fix this test fails while checking opacity of (0,0) itself and we don't hit this check.
Exception in thread "main" java.lang.Error: pixels at (0, 0) have different opacities: 0 vs ff000000
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, this is removed.
That was a redundant check intended to:
A. Reinforce what inspired this test
B. Double-check that ImageIO also did the right thing. (That is: the new GifComparison makes sure ImageIO and ToolkitImage are identical. But if they happened to both be wrong then GifComparison would be satisfied, so this confirmed the test gif file really was parsed correctly.)
This is in response to: openjdk#25044 (comment)
This is in response to: openjdk#25044 (comment)
OK. I switched back to this approach. (The downside being: now we'll invoke Arrays.fill(..) for all gifs with that disposal method. I assume (?) gifs with this architecture/problem are a small subset of that group; but it's impossible to quantify that hunch.) |
This makes the main() method much less useful, so I removed it too. (I originally used this class to explore a folder of hundreds of gifs to look for discrepancies. But the discrepancies were rarely only on the last frame.) This is in response to: openjdk#25044 (comment)
This can be used by multiple gif tests in this directory. This is in response to: openjdk#25044 (review)
This is in response to: openjdk#25044 (review)
I wrote a new class GifBuilder that programmatically creates the test gif file; and it can generate test files for some of the other related pending bugs.
I think we need 3 frames. The test currently resembles:
If I comment out any one of these three frames then this test passes when I run it against the master branch. For additional context: |
This makes the main() method much less useful, so I removed it too. (I originally used this class to explore a folder of hundreds of gifs to look for discrepancies. But the discrepancies were rarely only on the last frame.) This is in response to: openjdk#25044 (comment)
This can be used by multiple gif tests in this directory. This is in response to: openjdk#25044 (review)
This is an extension of work for this PR: openjdk#25044 (review)
This makes the main() method much less useful, so I removed it too. (I originally used this class to explore a folder of hundreds of gifs to look for discrepancies. But the discrepancies were rarely only on the last frame.) This is in response to: openjdk#25044 (comment)
This can be used by multiple gif tests in this directory. This is in response to: openjdk#25044 (review)
This is an extension of work for this PR: openjdk#25044 (review)
This resolves a gif parsing bug where an unwanted opaque rectangle could appear under these conditions:
Previously: the GifImageDecoder would leave the saved_image pixels as zero when they were supposed to be transparent. This works great if the transparent pixel index is zero, but it flood fills the background of your frame with the zeroeth color otherwise.
I wrote four PRs that share the GifComparison class in this PR. Once any of them clear code review the other PRs will be much simpler:
Progress
Issue
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25044/head:pull/25044
$ git checkout pull/25044
Update a local copy of the PR:
$ git checkout pull/25044
$ git pull https://git.openjdk.org/jdk.git pull/25044/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 25044
View PR using the GUI difftool:
$ git pr show -t 25044
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25044.diff
Using Webrev
Link to Webrev Comment