FontMetrics returning wrong character size - java

since I work on a LWJGL project, it encountered a problem to display text.
In theory I'd like to iterate through the (extended) ascii table and save each texture of all the chars inside of a ArrayList.
But my problem is that it won't get the right dimensions while creating a char image. The dimensions seem to be 1 (width) x 3 (height)
Code:
private static BufferedImage createCharImage(Font font, char c) {
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
// As you see, I set the font size to 100 for testing
font.awtFont.deriveFont(100);
graphics.setFont(font.awtFont);
FontMetrics fontMetrics = graphics.getFontMetrics();
graphics.dispose();
// Here the problem appears that the dimensions seem to be w: 1 and h: 3
Vector2i charDimensions = new Vector2i(fontMetrics.charWidth(c), fontMetrics.getHeight());
if (charDimensions.x == 0) {
return null;
}
image = new BufferedImage(charDimensions.x, charDimensions.y, BufferedImage.TYPE_INT_ARGB);
graphics = image.createGraphics();
graphics.setFont(font.awtFont);
graphics.setPaint(java.awt.Color.WHITE);
graphics.drawString(String.valueOf(c), 0, fontMetrics.getAscent());
graphics.dispose();
return image;
}
May anyone help me out there please?
Or if you have another idea to fix this, just let me know

The line font.awtFont.deriveFont(100); in your code currently literally does nothing except waste time by creating a new font and then immediately discarding it and using the original font instead.
Derive the new font and then use it:
....
Font bloodyHuge = font.awtFont.deriveFont(100);
graphics.setFont(bloodyHuge);
...

Related

Font face value returns unexpected value ("Dialog") from image

This code snippet creates an image which contains text. I set the font to Serif. However, when I query the resulting image later on for its font face, it returns Dialog. I don't understand why this is.
BufferedImage img = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(img, 0, 0, 200, 100, this); // "this" refers to my custom JPanel which I am setting in the JFrame.
g2d.setPaint(Color.red);
g2d.setFont(new Font("Serif", Font.BOLD, 20));
System.out.println("from g2d object: " + g2d.getFont().getFamily()); // outputs "Serif"
String s = "Hello, world!";
FontMetrics fm = g2d.getFontMetrics();
int x = img.getWidth() - fm.stringWidth(s) - 5;
int y = fm.getHeight();
g2d.drawString(s, x, y);
g2d.dispose();
System.out.println("from image: " + img.getGraphics().getFont().getFamily()); // outputs "Dialog" (expected "Serif")
I understand that Dialog is one of the logical fonts in Java, but if the font is set to be something else, and Font.getFontName() returns the font face for the given font, why isn't it returning Serif as set in the Graphics2D object?
UPDATE: Calling g2d.dispose() before or after the last System.out.println() makes no difference. Both ways, it still prints out Dialog.
BufferedImage.getGraphics() returns the result of BufferedImage.createGraphics(). And, following the trail, we end up at that method of SunGraphicsEnvironment:
/**
* Returns a Graphics2D object for rendering into the
* given BufferedImage.
* #throws NullPointerException if BufferedImage argument is null
*/
public Graphics2D createGraphics(BufferedImage img) {
if (img == null) {
throw new NullPointerException("BufferedImage cannot be null");
}
SurfaceData sd = SurfaceData.getPrimarySurfaceData(img);
return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
}
We can clearly see that it uses the defaultFont, hardly connected to the image (unless getPrimarySurface() does change the defaultFont - I guess not [I could not find it being changed]).
Source code can be found here
Setting the Font of a Graphics will not change anything in a BufferedImage. The Font is used by the Graphics to draw the text onto the image. If a new Graphics is obtained from the image (using getGraphics() or createGraphics()), it will have the defaultFont as defined in the GraphicsEnvironment.
Do this:
BufferedImage img = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(img, 0, 0, 200, 100, null); // "this" refers to my custom JPanel which I am setting in the JFrame.
g2d.setPaint(Color.red);
g2d.setFont(new Font("Serif", Font.BOLD, 20));
System.out.println("from g2d object: " + g2d.getFont().getFamily()); // outputs "Serif"
String s = "Hello, world!";
FontMetrics fm = g2d.getFontMetrics();
int x = img.getWidth() - fm.stringWidth(s) - 5;
int y = fm.getHeight();
g2d.drawString(s, x, y);
System.out.println(g2d.getFont().toString()+"-"+img.getGraphics().getFont().toString()+" from image: " + img.getGraphics().getFont().getFamily());
g2d.dispose();
System.out.println("from image: " + img.getGraphics().getFont().getFamily());
As you can see g2d retains its beloved Serif notation. The image is a thing of itself. Each time you call createGraphics on the image you get a new thing.
Everyone is making this more complicated than it needs to be. The answer is quite simple: the getGraphics() method of Image always creates a brand new Graphics object. From the documentation:
Creates a graphics context for drawing to an off-screen image.
That’s all there is to it. You set the font in one Graphics object, then you created another Graphics object. Of course the new Graphics object doesn’t know anything about the state of the first Graphics object. That would be like expecting that turning on one car’s headlights could somehow cause all cars to turn on their headlights.
You can get the expected result by only calling getGraphics() once.
System.out.println("from image: " + g2d.getFont().getFamily());
TL:DR The font is not accessible in Graphics or Graphics2D.
Whether you invoke BufferedImage#createGraphics() directly or BufferedImage#getGraphics() the end result is the same: they both create a new graphics object. More specifically, the former returns a new Graphics object and the latter returns a Graphics2D object. Both are abstractions of the concrete type returned which is an instance of SunGraphics2D. Unfortunately, this object does not store any information set, related to the font, in the graphics object. So basically, I cannot get the data I need from the image object.
In contrast, ProxyGraphics2D and PeekGraphics - both subtypes of the abstract class Graphics2D class do store such information in a Graphics2D global variable called mGraphics. This object is accessed via the getDelegate() method, which is obviously not part of the Graphics or Graphics2D API. Unfortunately, both of these subclasses are part of the sun package which is no longer accessible. Examining these classes clearly shows that methods like setFont() do save the font in this delegate (Graphics2D) object.

Why is my BufferedImage different when drawn to canvas?

Original
https://drive.google.com/file/d/1B3xxfWkGsMs2_MQ_bUQ8_ALYI0DL-LIo/view?usp=sharing
When saved to file
https://drive.google.com/file/d/1z5euXupeHmiFebch4A39fVqGukoUiK0p/view?usp=sharing
When printed to canvas
https://drive.google.com/file/d/1VouD-ygf0pPXFFx9Knr4pv44FHMtoqcV/view?usp=sharing
BufferedImage temp = bImg.getSubimage(100, 100, (int)imgWidth - 100, (int)imgHeight - 100);
try{
ImageIO.write(temp, "png", new File("test.png"));
}catch(Exception e){
e.printStackTrace();
}
gc.drawImage(SwingFXUtils.toFXImage(temp, null), 100, 100);
For some reason if I print an image to the canvas, it is different than if I save the same image to a file. When I save it to a file it correctly calculates the subImage but when I print it to the canvas it disregards the x and y coords I give it and takes a subImage using (0,0) as (x,y) with the given width and height.
From the documentation of the getSubimage method:
Returns a subimage defined by a specified rectangular region. The returned BufferedImage shares the same data array as the original image.
The sub-image is just a “window” into the original image; they are using the same pixel data.
The SwingFXUtils.toFXImage documentation states:
Snapshots the specified BufferedImage and stores a copy of its pixels into a JavaFX Image object, creating a new object if needed.
While it would certainly make sense to only copy the pixels in the source image’s dimensions, the above words don’t make it completely clear that it won’t copy the entire pixel data buffer, thus ignoring the boundaries of a sub-image. I would consider this a bug, but I can see where there might be an argument that it’s not.
In the meantime, you can work around this by extracting a sub-image yourself:
BufferedImage cropped = new BufferedImage(
(int) imgWidth - 100,
(int) imgHeight - 100,
bImg.getType());
Graphics g = cropped.getGraphics();
g.drawImage(bImg, -100, -100, null);
g.dispose();
gc.drawImage(SwingFXUtils.toFXImage(cropped, null), 100, 100);

Performing setRGB on BufferedImage changes pixel to black instead of color

** Important update, see below! **
I am creating a program that changes the pixels of a BufferedImage to a certain color when that pixel fulfills a set of conditions in Java. However, when I write the image to disk, the pixels that should be colored are instead black.
First I define the color, using RGB codes:
Color purple = new Color(82, 0, 99);
int PURPLE = purple.getRGB();
Then I read the image I want to alter from a File into a BufferedImage called "blank":
BufferedImage blank = ImageIO.read(new File("some path"));
Now, loop through the pixels, and when a pixel at location (x, y) matches a criteria, change its color to purple:
blank.setRGB(x, y, PURPLE);
Now, write "blank" to the disk.
File output = new File("some other path");
ImageIO.write(blankIn, "png", output); // try-catch blocks intentionally left out
The resulting file should be "blank" with some purple pixels, but the pixels in question are instead black. I know for a fact that the issue is with setRGB and NOT any import or export functions, because "blank" itself is a color image, and gets written to file as such. I read around and saw a lot of posts recommending that I use Graphics2D and to avoid setRGB, but with no discussion of pixel-by-pixel color changing.
I also tried direct bit manipulation, like this:
blank.setRGB(x, y, ((82 << 16) + (0 << 8) + 99));
I'm probably doing that wrong, but if I put it in correctly it wouldn't matter, because the pixels are getting set to transparent when I do this (regardless of what the numbers say, which is very strange, to say the least).
** When I try this:
blank.setRGB(x, y, Color.RED.getRGB());
My output file is grayscale, so that means setRGB is, in fact, modifying my picture in grayscale. I think this is actually a rather simple issue, but the solution eludes me.
Based on the insights in https://stackoverflow.com/a/21981173 that you found yourself ... (a few minutes after posting the question) ... it seems that it should be sufficient to simply convert the image into ARGB directly after it was loaded:
public static BufferedImage convertToARGB(BufferedImage image)
{
BufferedImage newImage = new BufferedImage(
image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
The original image that was imported into Java was actually grayscale, so when Java read it into the BufferedImage, it simply imported it as a grayscale BufferedImage. By adding a very small but imperceptible colored dot in the corner of my image, I was able to get Java to output a correctly colored image.
Unfortunately, this is only a half solution, because I do not know how to fix this programmatically.
SOLUTION:
Convert the BufferedImage from grayscale to ARGB with this snippet:
BufferedImage blank2 = blank;
// Create temporary copy of blank
blank = new BufferedImage(blank.getWidth(), blank.getHeight(), BufferedImage.TYPE_INT_ARGB);
// Recreate blank as an ARGB BufferedImage
ColorConvertOp convert = new ColorConvertOp(null);
// Now create a ColorConvertOp object
convert.filter(blank2, blank);
// Convert blank2 to the blank color scheme and hold it in blank
You want to add this right after blank = ImageIO.read(new File("some path")).

String length in pixels in Java

Is there a way to calculate the length of a string in pixels, given a certain java.awt.Font object, that does not use any GUI components?
that does not use any GUI components?
It depends on what you mean here. I'm assuming you mean you want to do it without receiving a HeadlessException.
The best way is with a BufferedImage. AFAIK, this won't throw a HeadlessException:
Font font = ... ;
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
FontMetrics fm = img.getGraphics().getFontMetrics(font);
int width = fm.stringWidth("Your string");
Other than using something like this, I don't think you can. You need a graphics context in order to create a FontMetrics and give you font size information.
You can use the Graphics2D object to get the font bounds (including the width):
Graphics2D g2d = ...
Font font = ...
Rectangle2D f = font.getStringBounds("hello world!", g2d.getFontRenderContext());
But that depends on how you will get the Graphics2D object (for example from an Image).
This gives the output of (137.0, 15.09375) for me. I have no idea what the units are, but it certainly looks proportionally correct and doesn't use Graphics2D directly.
Font f = new Font("Ariel", Font.PLAIN, 12);
Rectangle2D r = f.getStringBounds("Hello World! Hello World!", new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT));
System.out.println("(" + r.getWidth() + ", " + r.getHeight() + ")");
I needed to get length and width of a string before paintComponent was called so I could size the enclosing panel to the text dimensions. None of these techniques provided a sufficiently width and I did not have a Graphics object available. I resolved the issue by setting my font to "Monospaced".

Java padding image

I am working on creating an online image editing tool.Looking for some refernce how can I add an image with white space on right side.For example see this image
Presumably, you want to create a new image from an existing image, where the new image has white space on the left and right?
Suppose the unpadded image was a BufferedImage and is called 'image'. Suppose the amount of whitespace you want on each side is 'w'. What you want to do is create a new BufferedImage wider than the original, then paint the entire thing white, and finally draw the smaller image on top of it:
BufferedImage newImage = new BufferedImage(image.getWidth() + 2 * w, image.getHeight(), image.getType());
Graphics g = newImage.getGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, image.getWidth() + 2 * w, image.getHeight());
g.drawImage(image, w, 0, null);
g.dispose();
If anyone comes upon a similar problem, I would definitively recommend imgScalr. You can add padding with literally one line imageSource= Scalr.pad(imageSource,pad,Color.White);.
Create a new BufferedImage object of the right size; use Graphics.fillRect() to paint it white; draw the image into the top-left corner with drawImage(); then save your new image.

Categories