JDK-8072682 : getBounds call on graphics.getDeviceConfiguration() returning cached information
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 6u16,7,7u45,8,8u31,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86
  • Submitted: 2015-02-03
  • Updated: 2017-11-23
  • Resolved: 2015-10-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b89Fixed
Related Reports
Duplicate :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.7.0_45", java version "1.6.0_06". Since it is a simple bug I am sure it is there in java 8 also.

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
When I call graphics.getDeviceConfiguration().getBounds() on a Graphics2D object got on a BufferedImage read from a file I continue to get the previous image dimensions after reading a new image.

Please see details with sample program in steps to reproduce.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Below is a sample program which prints image bounds of jpeg files passed as command line args.

Try calling it with paths to 2 images of different dimensions (Example: java GraphicsBug image1.jpg image2.jpg. The dimension of the first image is printed the second time also.

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;


public class GraphicsBug {

    public static void loadJpeg(String imagePath) throws IOException {
        ImageReader reader;
        File srcImageFile = new File(imagePath);
        ImageInputStream iis = ImageIO.createImageInputStream(srcImageFile);
        reader = (ImageReader) ImageIO.getImageReaders(iis).next();
        reader.setInput(iis);
        BufferedImage image = reader.read(0);
        Graphics2D graphics = image.createGraphics();
        System.out.println("Image Bounds from Graphics = " + graphics.getDeviceConfiguration().getBounds());
    }

    public static void main(String [] args) throws IOException {
        for (int i = 0; i < args.length; i++) {
            loadJpeg(args[i]);
        }
    }
}


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The correct image dimensions should be printed for each image. Below are two runs with Expected results:

java GraphicsBug image1.jpg image2.jpg

Image Bounds from Graphics = java.awt.Rectangle[x=0,y=0,width=496,height=64]
Image Bounds from Graphics = java.awt.Rectangle[x=0,y=0,width=672,height=80]

java GraphicsBug image2.jpg
Image Bounds from Graphics = java.awt.Rectangle[x=0,y=0,width=672,height=80]
ACTUAL -
In the first run with 2 images the dimensions of the 1st image is duplicated:

java GraphicsBug image1.jpg image2.jpg
Image Bounds from Graphics = java.awt.Rectangle[x=0,y=0,width=496,height=64]
Image Bounds from Graphics = java.awt.Rectangle[x=0,y=0,width=496,height=64]


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;


public class GraphicsBug {

    public static void loadJpeg(String imagePath) throws IOException {
        ImageReader reader;
        File srcImageFile = new File(imagePath);
        ImageInputStream iis = ImageIO.createImageInputStream(srcImageFile);
        reader = (ImageReader) ImageIO.getImageReaders(iis).next();
        reader.setInput(iis);
        BufferedImage image = reader.read(0);
        Graphics2D graphics = image.createGraphics();
        System.out.println("Image Bounds from Graphics = " + graphics.getDeviceConfiguration().getBounds());
    }

    // Pass Paths to 2 or more images with different dimensions and see the bound printed
    public static void main(String [] args) throws IOException {
        for (int i = 0; i < args.length; i++) {
            loadJpeg(args[i]);
        }
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Do not use graphics.getDeviceConfiguration to get the image bounds get it directly from image.getWidth() and image.getHeight()


Comments
See side-effect in Marlin Dasher (infinite line)
23-11-2017

I agree Sergey's comment that this API behavior change enlightened a bug in the Java2D renderer, i.e in the Marlin renderer for JDK9. However this clipping issue exists for a long time: both Ductus & Pisces suffer the same problem on JDK8. The coming Marlin 0.8.2 patch provides new clipping filters for both Stroker & Filler, but some work remains to be done to handle clipping in the Dasher class (preserving dash phase has some complexity). I will create another bug about clipping in Marlin's Dasher. Finally the JDK9 release notes should mention that this bug has this 'known' problem (side-effect) caused by BufferedImage's Graphics.getDeviceConfiguration().getBounds()
21-11-2017

I do not see a reason why the provided solution returns non-real values. This is a virtual graphic device which can have any possible bounds including MAX_INT, and this virtual device is used for a number of BufferedImages which can have different size. To depend on this value is the same thing as to depend on a bounds of the screen when draw something to the window on the screen or to draw to the VolatileImage. But the hang in the code below looks like a problem in our code. It is unclear why we allow the user to draw(or to try to draw) such huge lines, when obviously the clip is much smaller. g2.draw(normline); // <- infinite loop here (100% cpu)
21-11-2017

Is this change described in JDK9 release notes ? JOSM encountered that API behaviour change and the application code has been fixed to use graphics.getClipBounds() instead of graphics.getDeviceConfiguration().getBounds(). It was figured out as an infinite dashed line was emitted from 0 to Integer.MAX_INT = 2E9 causing the application to hang / slow down. See https://josm.openstreetmap.de/ticket/15535
20-11-2017

Yes, if it came from a "new BufferedImage()" (well, indirectly through the Image stuff) then that makes sense, but I think a similar thing can happen if you get it from a VolatileImage so we should check that case too. Also, a screen GC can createCompatibleImage() which may be implemented as a BufferedImage (or a subclass thereof), but those might want to copy the bounds of the screen GC that they were created from (if they don't already reuse the GC of the screen itself) so that one gets the bounds of the screen associated with this "image that is compatible with that screen GC"...
07-02-2015

It is a software image that is being created here, so MAXINT sounds like it could work for that. That image could later end up being copied to a texture that has a smaller limit So far as I can see the SunVolatileImage constructor places no limits on the dimensions. Perhaps there's always a software backup if its too large for available hardware resources so it ends up being like the BI case.
06-02-2015

I'm trying to construct a scenario where someone might care and the best I can think of is that if someone got the GC of a display device then they may think "there is no reason to construct a backbuffer larger than GC.getBounds() because that will be the size of the screen. If they then somehow were fed one of these fake "Image GCs" then they might be limited in how large of an image they might construct. I'd say that if the various creat*Image() methods on a GC are creating resources that have maximum sizes (such as a volatile image that might be limited by maximum texture sizes), then we should report those bounds. If the methods all create pure sw images then MAXINT makes sense if we place no limits internally on CompatibleImage...
06-02-2015

Ah. I found this other bug : https://bugs.openjdk.java.net/browse/JDK-4494651 - of which this new report really is a duplicate - where the opinion is that reporting the bounds as being the values of a particular image was wrong to begin with. If you can theoretically (with enough memory/heap) create an image with MAXINT, MAXINT bounds, then that is what should be reported in all cases, not the bounds of a (random) image. If we make that change are we likely to break anyone ? I suppose since the current values are random its not likely anyone can be relying on them anyway but I wouldn't be too surprised if (say) some code was allocating some backing corresponding to the largest bounds seen so far. I expect if we change it to MAXINT we'll find those cases quite quickly !
06-02-2015

If I correctly interpret the comments in https://bugs.openjdk.java.net/browse/JDK-4494646 then the config is created lazily and data shows its rarely actually requested, so there should only be minor impact here.
06-02-2015

The cache is in BufferedImageGraphicsConfig.java and has been there since 1997 and so was in JDK 1.2 The getBounds() method on GraphicsConfiguration was added in 1999 for JDK 1.3. So it appears to me as if no one realised at that time (1999) that there was a cache dependent on being able to share the GraphicsConfiguration across images of the same type, that now has to be one per-image. It seems possible there will be a detectable overhead to losing this cache for some code but we probably need to delete the cache anyway. I doubt its worth keeping a cache that has to additionally key on the image bounds even if it turns out that is the only other per-image data.
06-02-2015

Simplified test: import java.awt.*; import java.awt.image.BufferedImage; public class DeviceBounds { public static void main(String[] args) { // NB: all images have the same type BufferedImage[] images = new BufferedImage[] { new BufferedImage(200, 200, BufferedImage.TYPE_3BYTE_BGR), new BufferedImage(400, 400, BufferedImage.TYPE_3BYTE_BGR), new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR) }; for (BufferedImage i : images) { Graphics2D g = i.createGraphics(); System.out.println(g.getDeviceConfiguration().getBounds()); g.dispose(); } } }
06-02-2015