I am working on an app that has a Swing user interface. Text is being written to the window with a Graphics2D object (specifically SunGraphics2D) using the drawString method.
I have an issue where on high DPI displays some of the text renders very poorly, for example on a 4K monitor set to 150% scale.
The rendering can be improved by using -Dsun.java2d.uiScale=1.0, but this is simply disabling scaling, so everything is tiny.
If I had to guess what was happening, I would say that the graphics is being rendered at 1.0 scale, anti-aliasing is being applied, and then it is being scaled 1.5 times using Nearest Neighbour Interpolation. My reasoning for this is that it appears as though sometimes "anti-aliased pixels" (i.e. for black text, the light grey ones) are sampled for the scaling operation, which causes specific letters to be rendered unusually.
For example, this image shows the string "123" (I have enlarged it by 200% to make the issue more obvious):
As you can see the line that makes up the bottom of the "2" is 1px high, but the anti-aliasing is 2px high.
Another example with the string "555", where the base of the "5" is again very skinny:
Interestingly this doesn't affect all text, for example the text on JButton and JLabel objects renders at the correct scale and looks good:
This issue does also affect images which are rendered within the window, but I am not very familiar with the image rendering classes of this particular application, so I cannot provide more info, other than to say this might point to the Graphics object being the issue.
I have tried to create a minimum example of the issue, but unfortunately when I create a brand new project the text renders perfectly, so it seems that this issue is specific to the application I am working on, which had many thousands of lines of code.
Is there something I should be looking into or trying? Perhaps there is a "Scaling Mode" for the graphics object, which is set to "Nearest Neighbour" or something like that?
I have tried to use a few RenderingHints such as RenderingHints.VALUE_FRACTIONALMETRICS_ON, but that didn't make any difference.
JDK is Eclipse Temurin 11.
Edit:
In case it is helpful, here are the RenderingHints being used:
Related
Can you suggest a method of identifying the source of a rendering error from a debugger?
Misko Hevery classifies bugs into three categories:
Logical
Wiring
Rendering
Its clear to me that my problem is a rendering bug.
I have a Swing application with a Panel that contains multiple layers. Rendering all the layers can take a significant amount of time so the application uses a thread pool to render layers and tiles from layers into BufferedImages. When it comes time for the Event Dispatch Thread to render the panel the most-recently rendered BufferedImages are drawn to the screen.
This setup has performed adequately.
A new feature requires that a certain layer type support transparency. Something, somewhere isn't preserving the transparency. The error could be in a number of places, possibly in the implementation of the objects to be rendered, the error could possibly be in the offline rendering thread implementation. Its possible that the many BufferedImages aren't combined together correctly in the EDT rendering code.
I'm not asking anyone to look at the code and tell me where the error is.
What I want to know is what techniques people have found particularly effective in troubleshooting a Graphics2D rendering issue.
I'm a big supporter of unit tests but I'd prefer to start with another technique.
Is there a method or trick to visually inspect a BufferedImage or Graphics2D object from the debugger?
In the Netbeans Variables and Watches windows Netbeans sometimes uses PropertyEditors to display variable values. In this example image the value of foregroundColor and backgroundColor are shown as small swatches of the Color's value.
Is there an easy way to add/enable a Netbeans PropertyEditor which would display the contents of a BufferedImage?
I could temporarily sprinkle the code with method calls to write the various BufferedImages encountered to the disk such that they could then be inspected offline. It might work but it would be tediious to match the file on disk to the source code.
What would you do?
You might compare your approach to the one shown here with regard to clearing the buffer:
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(0, 0, w, h);
In the worst case, you can break at a point in which your image is accessible and set a watch on the expression image.getRGB(0,0) with the display set to hexadecimal. The high order byte is the alpha value: FF is opaque, and 00..FE represents varying transparency.
I have a custom report which draws via Graphics2D, and uses a lot of tiny BufferedImage sprites. PrinterJob.print() seems to be calling Printable.print() roughly once for each sprite (the actual count can vary both ways), so some pages are re-rendered 150 times... This causes printing to be unacceptably slow, about 10 seconds for two pages.
I found this: Why does the java Printable's print method get called multiple times with the same page number?
But it doesn't appear to explain my particular problem (or only partially explains it). I created a test report which has only a few sprites, and there was a small number of resizes that went up and down as I added and removed images on either the vertical or horizontal axes.
When printing to a PDF using Bullzip, I noticed that after zooming in on the images, they are being scaled up using a bilinear or bicubic algorithm. One of these images, which is unique in having an indexed color palette, does not appear to be scaled. I confirmed that the scaling is a Java behavior and not being performed by Bullzip by printing to a real printer and observing the same images being scaled versus not.
So it strikes me as the print API trying to rescale images to whatever DPI it has in mind, but for some reason it's calling Printable.print() each time it encounters an image that it deems as needing this treatment.
How do I fix this behavior? I tried setting rendering hints on the Graphics2D that I get when Printable.print() is called, to no avail. I don't know what else to do short of try to find and examine the print API's source code.
I think I just figured it out by accident. A report I just modified now draws an image over some geometry, and I noticed that the part of the geometry that's behind the box of the image is being rasterized and looks blurry compared to outside of the box. The image in question (and all other than the one indexed color image) has an 8 bit alpha channel.
I noticed before that Java's print rasterizer doesn't like things with translucency (one report which used it was being completely rasterized at I think 300dpi...), but I forgot that these images also had alpha channels.
When I get a chance, I'm probably going to fix this by further increasing the images' resolution and using 1 bit alpha. When scaled down for screen viewing, it will have a few bits of alpha again and look okay.
I'm using the JAI library to do adjustments on a series of images, each about 1300x1000 in size. I adjust pixel intensities with "Rescale" and add text and lines with the Graphics2D object from the TiledImage.createGraphics() method. Then I resize the image to fit the screen with "subsampleaverage" and render to screen with Graphics2D.drawRenderedImage(). Up to this point, there is little slowdown, with rendering taking about 40-60 milliseconds.
However, if I only add the text and lines, the display slows down to 100-200 milliseconds. I can't seem to figure out why this is, as adding the text after or before adjust pixel intensities is fine.
I've been searching through the site, but I can't seem to find any concrete answer. Many suggestions have been to use BufferedImages, but converting from PlanarImages to BufferedImages seems to also have a slowdown issue.
Apparently text is still rendered very slowly in Java. The glyphs for each Font object has to be rendered and painted on the Graphics object. With a lot of text on the object, the Font object along with all the used Glyphs are recreated, causing a massive slowdown.
Even using JOGL, there is a significant slowdown. But using the same TextRenderer object alleviates this by creating a single Font object and reusing it as long as the TextRenderer is alive. Of course, this restricts you from using multiple Font objects, as JOGL has yet to implement a setFont function, requiring you to create a new TextRenderer object for each new font, font style, and font weight.
Hope this helps anyone with similar issues.
I was trying to create a rectangular image where will be 2 things.
A label (100% width and 20% height)
A Text Area (100% width and 80% height)
So lablel will be at top and text area will be at bottom
so I tried to create a JFrame and place both components there. Then I am creating its image with JFrame.createImage(width, height) and in last I used ImageIO.write(). but problem was, There was some extra blank space around the components in Image. When I tried to set its bound then it create an exact image but this image works perfectly on Windows but it doesn't work on Mac. Any idea why?
Is there also another easy way where I can achieve this. I've spent 2 days but couldn't found any solution.
Please advice
Thanks in adnvace
Rendering using Swing components is very versatile, but the user interface delegate for each component varies by platform. Similarly, font metrics also vary by platform and vendor. If you need very fine control over the placement of text, you can access the graphics context's font metrics as seen in this example, and you can mitigate aliasing as shown here.
I know how to draw a rectangle onto a JPanel, but how can I paint a rectangle to the screen so that the rectangle appears to be floating? More specifically, a non-filled rectangle. My thought is to use a transparent JFrame with a rectangle drawn on it; however, this makes all of the content in the JFrame transparent.
My Solution
So I think there are many ways of going about this, some more complex than others, some more practical than others.
I tried the splash screen. The problem with that is you need to pass VM parameters "-splash " when you run. I created a manifest file to automate this/put the parameters into eclipse; but then the code is dependent on the .gif file and I can't change the size/position of the rectangle easily. Similar problems occur while faking it via screen screenshot. Good suggestions though, I learned some pretty cool stuff.
So, back to what I did. I used a JFrame, got the content pane and set the background to red (what ever color you want), then set the frame undecorated which removes the titlebar and border of the window. This created a floating rectangle which I could easily change the size and location of (frame.setSize, .setLocation). I have yet to make this a non filled rectangle, I tried internal frames and layeredpanes, but no success.
JFrame is a heavyweight component, and those were always opaque for the longest time. However, since Java 6u10, there is an inofficial API for transparent windows in the class com.sun.awt.AWTUtilities, which will most likely become official in Java 7. In earlier versions, the only way to simulate this kind of thing was to fake it via screenshots taken with java.awt.Robot
You would probably have to have parts of the window transparent while the actual drawn rectangle is not. I doubt there is a platform-agnostic solution for this so you would need to resort to JNI for this. Depending on what you need to do it might also be nice to make the window invisible for clicks which would need other tricks as well.
https://github.com/twall/jna/
That project offers a library and has examples of a clock and info balloons that are semi-transparent and transcend even what you're trying to do. The demos work well on Windows but I couldn't speak to how cross platform they are.
You might want to look at JLayeredPane. The demo on this page shows partially what you want, however when painting your rectangle you'll need to set your paint to transparent:
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
g2d.setComposite(ac);
g2d.drawImage(image, x, y, this);