I'm building an app which among other things allows the user to insert a text into a PDF, using a layer.
The position of the text in the PDF page can be set using the app, which renders the PDF using ICEPdf inside a JPanel. After selecting the position and the size of the layer, the app renders it to the PDF using iText (v. 5.3.2).
The problem I'm facing is that the font rendering from Swing is sightly different from the final result in the PDF.
Here are some screen-shots, both using the Helvetica plain font inside the same bounding box:
Rendering a text with Swing:
protected void paintComponent(Graphics g){
//for each line...
g.drawString(text, b0, b1);
//b0 and b1 are computed from the selected bounding box for the text
}
I have this:
Rendering a text with iText:
PdfTemplate t; //PdfTemplate is created elsewhere
ColumnText ct = new ColumnText(t);
ct.setRunDirection(PdfWriter.RUN_DIRECTION_NO_BIDI);
ct.setSpaceCharRatio(1);
ct.setSimpleColumn(new Phrase(text, font), b0, b1, b3, b4, font.getSize(), Element.ALIGN_BOTTOM);
//b0, b1, b2 and b3 are the bounding box of the text
ct.go();
I have this:
So the question is: what can be done to make Swing and iText render fonts exactly the same way? I can tweak Swing or iText, so no matter what code is modified, I need a truly WYSIWYG experience for the user.
I tried with other fonts and types, but still there are some differences between them. I think I'm missing some configuration.
Thanks.
Edit:
Font in Java is measured in Pixels, but iText measures in Points, and we know there are 72 points per inch, and for a standard windows machine there are 96 pixels per inch / dpi.
First we need to find the difference between a point and a pixel:
difference = 72 / dpi
0.75 = 72 / 96
Next we can multiply the Java font size with the difference to get the iText font size, if the java font size is 16, then the iText font size should be 12 when used with 96dpi.
iTextFontSize = difference x javaFontSize
12 = 0.75 x 16
On a windows machine 96dpi is often the norm, but remember that is not always the case, you should work it out for each different machine.
Original Post
I believe the difference is caused by rendering hints.
Solution 1:
The best way to do it would be to draw everything on a buffered image. Then the buffered image is drawn to the swing component and the exact same buffered image is drawn to the PDF as well, that way there should be no difference between them.
Alternate Idea:
Another idea that may not work as well, but will work better with your existing code is to draw the contents of the swing component directly to a buffered image then draw the buffered image to the PDF.
The following is a quick example that will draw the contents of a JPanel to a buffered image with a few different rendering hints. (Change the hints to suit your swing component)
public BufferedImage createImage(JPanel panel)
{
BufferedImage swingComponent = new BufferedImage(
panel.getHeight(), panel.getWidth(),
BufferedImage.TYPE_INT_RGB);
Graphics2D g = swingComponent.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.dispose();
return swingComponent; //print this to your PDF
}
So the question is: what can be done to make Swing and iText render fonts exactly the same way?
Exactly the same? Very little. Presuming that you supply the same font characteristics to both, there may still be differences introduced in rendering.
PDF files (or really, the text phrases) hold font information—family, size, style, etc.—but it is your PDF reader that renders the text to your device. Depending on what program you use, it can use the platform's renderer or it can choose one of its own.
AFAIK, Java Swing uses its own font rendering engine, as distinct from the underlying platform's renderer. This makes it look more consistent across platforms, but also makes it very unlikely that text will be rendered the same way any other program would.
Knowing that, your options are limited:
Use a PDF reader that also uses Swing font rendering. That's a fairly limiting solution.
Use your platform's rendering engine to draw into Swing. That sounds like iffy work, and also like something SWT would have solved. Maybe you could embed an SWT component?
Related
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:
There are about 100 jpeg & png color images used in our JavaFX-built desktop app which, when the window is resized, become stretched and blurry so I'd like to have all the graphics remade in a format that will allow them to be dynamically resized without losing quality. What image format or procedure should be used to do this?
Currently, each image is simply in an ImageView and resized as follows, but I'm open to other suggestions:
if(isSmall){
Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
double sh = visualBounds.getHeight();
Scale scale = new Scale(sh, sh, 0, 0);
root.getTransforms().setAll(scale);
}
As has already been mentioned SVG is probably the way to go for you. JavaFX does not support SVG directly but you can find support here
javafxsvg and here svg-to-fxml-converter for example.
You can't resize an image to be bigger than it is without it getting blurry for most common formats. Instead make sure your images are big enough so you only need to downscale them.
The only format I ever heard of that could upscale further was using fractal compression, but AFAIK it is not in common use.
I'm searching for a method to properly scale (almost) all of a JFrame's content. All the solutions I have tried so far had a huge lack in rendering speed. What I am looking for is a speed similar to what you have when scaling content on your smartphone.
The JFrame's content should be rescalable quickly and stay scaled even if you overdraw the JFrame with new content. It should also be flexible enough so it let's you choose which BufferedImage's (which is essentially the only type I'm drawing, I don't draw any other "shapes") to redraw. I'm drawing using an ordinary Graphics, resp. Graphics2D object.
What I've tried before is the Graphic2D's scale-method and using an AffineTransformat object to scale each BufferedImage individually:
g.scale(scalingFactorX, scalingFactorY);
or alternatively:
BufferedImage img = someImageToScale();
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scalingFactorX, scalingFactorY);
AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
return bilinearScaleOp.filter(img, new BufferedImage(targetWidth, targetHeight,
BufferedImage.TYPE_INT_ARGB));
where scalingFactorX/Y are the factors the content should be scaled by and targetWidth, resp. targetHeight denote the resulting (scaled) dimensions of the BufferedImage.
Both approaches are rather slow which seems to be because in both cases, each frame, the scaled version of the contents have to be recalculated. I feel like I'm missing something very obvious here.
While drawImage() can resample an image, it must must do so each time paintComponent() is called. As suggested here, AffineTransformOp lets you choose the interpolation type; use the fastest one that meets your image quality requirements. Once you've satisfactorily resampled an image, ensure that you do not inadvertently resample the image a second time when rendering in a particular Graphics context. Override getPreferredSize() to make the destination component the same size as the resampled image, as shown in these examples.
My images are only 32x32, but there are a large amount of them (~500).
Consider using the flyweight pattern to render only visible images. JTable and JList are examples, as outlined here.
Are there performance benefits or other advantages to using separate components?
You may need to prototype and profile to be sure, perhaps by comparing representative examples:
Typical component-based examples include the button-based game cited here, or the chess games examined here, here and here.
The tile-based game cited here illustrates a single tile panel and multiple accessory panels that all listen to a common game model.
I am trying to write some programs where I load images to a panel.
Here are my 5 questions:
1> Is there any restriction on what kind(extension) of images can be loaded? I tried to load a .bmp file, it didn't load even after I renamed it with .jpg. However, some other file that were with extensions such as .png or .jpg loaded.
2>Is there a way I can cut an image through java to create a new image. Say, I have a 600x600 pixel image and I want to create a new image by selecting a 200x200 pixel from the middle of the original picture.
3>Is there a way I can resize an image?
4>Can I add an image to a scrollpane?
5> Can I rotate an image by an angle ,say 30 degree?
That's it. A little elaboration with examples will be nice. Thanks in advance.
1> Is there any restriction on what kind(extension) of images can be
loaded? I tried to load a .bmp file, it didn't load even after I
renamed it with .jpg. However, some other file that were with
extensions such as .png or .jpg loaded.
Image I/O has built-in support for GIF, PNG, JPEG, BMP, and WBMP. Image I/O is also extensible so that developers or administrators can "plug-in" support for additional formats. For example, plug-ins for TIFF and JPEG 2000 are separately available.
Check the Reading/Loading an image tutorial page
2>Is there a way I can cut an image through java to create a new
image. Say, I have a 600x600 pixel image and I want to create a new
image by selecting a 200x200 pixel from the middle of the original
picture.
Two ways. Croping the image using Clipping with Graphics. But faster approach is to use BufferedImage.getSubimage(int x, int y, int w, int h) method.
BufferedImage image = ImageIO.read("image file");
image = image.getSubimage(50, 50, 200, 200);
This will crop an image at location(x, y) == (50, 50) and size 200 x 200.
3>Is there a way I can resize an image?
The discussion about the various approach will take a size of a blog. Read through the The Perils of Image.getScaledInstance() article for good insight.
However, a quick approach for example: with cWidth and cHeight
BufferedImage tmpImage = new BufferedImage(cWidth, cHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D)tmpImage.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(image, 0, 0, cWidth, cHeight, null);
There is working example done by MadProgrammer. It is also better to use external library such as this which does this works nicely.
4>Can I add an image to a scrollpane?
Direct adding is not suggested. use JLabel instead. Or use a custom component and override the paintComponent(Graphics g) function and draw inside it. There are some working example with these two approach. Generally working with JLabel is easier.
See this question answers. Mine including #MadProgrammer. And The custom painting official tutorial page.
5> Can I rotate an image by an angle ,say 30 degree?
Yes using the Graphics2D.rotate(double theta) function; There are actually much more things you can do with the Graphics2D API. Working examples are shown by MadProgrammer here and here.
Exposition:
I'm writting an OpenGL app in Java via JOGL. The built in fonts with GLUT are very basic. I want to be able to take a Java Font, render all the characters in it to a 2D array of bytes (a greyscale image representing the characters), which I can then load as a texture in OpenGL.
Problem:
I can load the textures in OpenGL; I can use JOGL. My only problem is going from the "Font java cn read" --> "2D greyscale image of all the characters step". What functions / libraries should I be using?
I'm not sure I quite understand what you're looking for. I think what you want is some code that will generate grayscale bitmap images of a given size for every glyph in a font.
There isn't a way (that I am aware of anyway) to get all the glyphs a font supports (oddly, you can get the number of glyphs... so yeah, I may just be missing something, bah). However, you can get glyph metrics for given characters quite easily.
Something along these lines should work for you.
HashMap<int[], Rectangle2D> generateGlyphs(int fontSize, String characters, Font font){
HashMap<int[], Rectangle2D> ret = new HashMap<int[], Rectangle>();
FontRenderContext rendCont = new FontRenderContext(null, true, true);
for(int i = 0; i < characters.length; i++){
Rectangle2D bounds = font.getStringBounds(characters.substring(i, 1), rendCont);
BufferedImage bi = new BufferedImage((int)bounds.getWidth(), (int)bounds.getHeight(), BufferedImage.TYPE_INT_GRAY);
Graphics g = bi.getGraphcs();
g.setFont(font);
g.drawString(characters.substring(i, 1), 0, (int)bounds.getHeight());
ret.put(bi.getData().getPixels(0, 0, (int)bounds.getWidth(), (int)bounds.getHeight()), bounds);
}
return ret;
}
Note that I'm clipping rather than rounding in some places, which could potentially be an issue.
Possible I'm missing something, but can java.awt not do most of this for you?
create a new BufferedImage
get its Graphics2D (image.getGraphics)
paint the text to the image
get the Raster (getData) from the buffered image.
dispose of the Graphics2D context when done.
Processing can render fonts in OpenGL, and it uses it's own in house open source class PFont to "generate grayscale bitmap images of a given size for every glyph in a font," as Kevin put it. I recommend you look at the source, which is here.