I'm currently drawing a string to a canvas with a specified font. I would, however, like to scale this font based on the window size.
Given a target string, how do I find the point size of a particular font face so that printing the target string will be either h units tall, or w units wide? Is there a linear relationship between point size and font dimensions?
I can think of very smelly ways to determine a relative point size (pick an arbitrary size and shrink / grow until the dimensions are within some epsilon of the target), but would rather do it more cleanly.
I want to do this with fonts-only, if possible, and not resort to affine transformations.
For the best metrics, I prefer TextLayout, illustrated here, but deriveFont(), suggested by #StanislavL among the answers here, is surprisingly agile and not at all malodorous.
Related
I am looking for the simplest (and still non-problematic) way to resize a BufferedImage in Java.
In some answer to a question, the user coobird suggested the following solution, in his words (very slightly changed by me):
**
The Graphics object has a method to draw an Image while also performing a resize operation:
Graphics.drawImage(Image, int, int, int, int, ImageObserver)
method can be used to specify the location along with the size of the image when drawing.
So, we could use a piece of code like this:
BufferedImage originalImage = // .. created somehow
BufferedImage newImage = new BufferedImage(SMALL_SIZE, SMALL_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = newImage.createGraphics();
g.drawImage(originalImage, 0, 0, SMALL_SIZE, SMALL_SIZE, null);
g.dispose();
This will take originalImage and draw it on the newImage with the width and height of SMALL_SIZE.
**
This solution seems rather simple. I have two questions about it:
Will it also work (using the exact same code), if I want to resize an image to a larger size, not only a smaller one?
Are there any problems with this solution?
If there is a better way to do this, please suggest it.
Thanks
The major problem with single step scaling is they don't generally produce quality output, as they focus on taking the original and squeezing into a smaller space, usually by dropping out a lot of pixel information (different algorithms do different things, so I'm generalizing)
Will drawGraphics scale up and down, yes, will it do it efficiently or produce a quality output? These will come down to implementation, generally speaking, most of the scaling algorithms used by default are focused on speed. You can effect these in a little way, but generally, unless you're scaling over a small range, the quality generally suffers (from my experience).
You can take a look at The Perils of Image.getScaledInstance() for more details and discussions on the topic.
Generally, what is generally recommend is to either use a dedicated library, like imgscalr, which, from the ten minutes I've played with it, does a pretty good job or perform a stepped scale.
A stepped scale basically steps the image up or down by the power of 2 until it reaches it's desired size. Remember, scaling up is nothing more then taking a pixel and enlarging it a little, so quality will always be an issue if you scale up to a very large size.
For example...
Quality of Image after resize very low -- Java
Scale the ImageIcon automatically to label size
Java: JPanel background not scaling
Remember, any scaling is generally an expensive operation (based on the original and target size of the image), so it is generally best to try and do those operations out side of the paint process and in the background where possible.
There is also the question whether you want to maintain the aspect ratio of the image? Based on you example, the image would be scaled in a square manner (stretched to meet to the requirements of the target size), this is generally not desired. You can pass -1 to either the width or height parameter and the underlying algorithm will maintain the aspect ratio of the original image or you could simply take control and make more determinations over whether you want to fill or fit the image to a target area, for example...
Java: maintaining aspect ratio of JPanel background image
In general, I avoid using drawImage or getScaledInstance most of the time (if your scaling only over a small range or want to do a low quality, fast scale, these can work) and rely more on things like fit/fill a target area and stepped scaling. The reason for using my own methods simply comes down to not always being allowed to use outside libraries. Nice not to have to re-invent the wheel where you can
It will enlarge the original if you set the parameters so. But: you should use some smart algorithm which preserves edges because simply enlarging an image will make it blurry and will result in worse perceived quality.
No problems. Theoretically this can even be hardware-accelerated on certain platforms.
Could anybody explain it to me?
You can't draw between pixels so why should I use float or double measuring when drawing? In Oracle's docs is written something about printer device, but it also can't paint between the smallest points. I don't understand it.
Let's say a simple line. This line has set width 1.3f. What is going on with it when it's drawn on:
display in windows (I believe it has 96 DPI)?
printer with 300 DPI?
AFAIK Java uses 72 DPI internally. So how is the math?
Several use cases come to mind.
Your graphics device might be scaled. For example I know of several applications which draw a window-filling image of the unit circle, i.e. a circle of radius 1, using an appropiate scaling of the graphics context.
You might be producing output for a vector-oriented target, like a PDF file. In that case, users might zoom in arbitrarily, and might expect a fair amount or precision even at high resolutions.
Printers, like you mention, might print at a resolution much higher than the screen, which is accomplished by a built-in zoom factor that maps default coordinate units to several times the device pixel size.
Anti-aliasing suggest sub-pixel resolution. The amount of color applied to a given pixel at the boundary of a geometric object will depend on the sub-pixel coordinates of said object.
None of the above would readily rule out using single precision floats, and in fact most G2D operations are available using floats as well. Using doubles is only important for really large zooms, really strong demands in terms of precision, and similar applications. But on the other hand, most computations are performed on doubles in any case, and the overhead of carrying these as far through the graphics pipeline as possible is often negligible. So when you ask me why to use double instead of float, I ask you “why not?”
From an xml file, I'm given a width, height and id. All of them can and do vary very quickly. Now, I'm asked to draw a rectangle using the width and height (an easy task), and place the id at its center. The id must not overflow out of the rectangle it's contained it.
For single-character strings, this is also easy - set the font size to the height, play a bit with the x position maybe, and it's centered. The problem is when it's multi-character strings.
So given a width and height and a string, how can you determine what font-size the string should appear in? Assume you have every bit of information you need on the rectangle you're drawing the string in.
[Edit]: I'm using the Graphics 2D class to draw everything.
Start with selecting a Font at your preferred (i.e. maximum) size.
Grab the FontRenderContext from your Graphics2D object using getFontRenderContext.
Use getStringBounds() on the Font to be rendered to get a Rectangle2D object for the specific String to be rendered. That object describes the final size of the String using that Font
Check if the size specified by that Rectangle2D is small enough.
4a. If it is small enough, you're done. Use the last Font you've checked.
4b. If it is too big, use Font.derive() to produce a smaller version of the Font and continue to use that and loop back to 3.
Don't quite have the time to give you a full working example, but here are a couple pointers that should get you going in the right direction. The graphics object you are using to draw with has a getFontMetrics() method, one of the methods on FontMetrics is stringWidth(String str) which gives you the width of a string in the current Font.
If the width is too big for your rectangle set the Font on the Graphics object to the same font just with a smaller size until it fits.
To horizontally center a string in a container (learned long ago in typing class in high school):
(rectangleWidth / 2) - (stringWidth / 2)
http://download.oracle.com/javase/1.5.0/docs/api/java/awt/FontMetrics.html
To create a Font with a smaller size, something like:
Font font = graphics.getFont();
Font smallerFont = font.derive(font.getSize() - 1);
graphics.setFont(smallerFont);
Hope this gets you going in the right direction.
I would recommend for this problem to remove as many unknowns as possible. In this case, the problem chiefly is that font characters can vary in width... well most. That's why I would use a good monospace font like courier new for the ID, that way you know what the width of each character is, you know the width of your rectangle and you know the number of characters in your string. You can simply reduce the pixel size of each character will till your string fits the available width.
Example, if the width of each character is 12px and you have 10 characters in your ID, then you need 120px to fit everything in. If you only have 80px available, it's simple math 80/10 = 8px font-size (reduce half a pixel for padding if you want.
Just my suggestion.
Let's say a string rendered with a 10-point font is 200 pixels wide. If I draw the same string with a 15-point font (a 50% increase), will its rendered width also increase by 50% (to 300 pixels)?
(Of course, this assumes no other changes in the font and the Graphics2D where the text is rendered.)
In other words, is there a direct linear relationship between a font's point size and the size text is rendered? Or are there other variables that prevent me from making any such assumptions?
You're close with your assumption.
However, the spacing between letters doesn't always scale exactly. I believe it has to do with integer division sometimes having a remainder, but I haven't looked at the Java FontMetrics class code in a while.
You're safest always creating a new Font with the appropriate font size, and calling the getStringBounds method on the FontMetrics class.
First problem: You have 400 pixels width to go on, and need to fit some text within that constraint as large as possible (thus, the text shall use that amount of space).
Throw in a new constraint: If the text is just "A", then it shall not zoom this above 100 pixels height (or some specific font size).
Then, a final situation: Linebreaks. Fit some text in the largest possible way within e.g. 400 x 150 pixels.
An obvious way is to simply start with point 1, and then increase until you can't fit it anymore. This would work for all three problems, but would be very crude. The fitting of a single line within bounds could be done by writing it with some fixed point size, check the resulting pixel bounds of the text, and then simply scale it with a transform (the text scales properly too then, check out TransformUI).
Any ideas of other ways to attack this would be greatly appreciated!
As what you are modelling is complex, especially with line breaks, then your initial proposal of trying all sizes is along the right lines, especially if it needs to be accurate.
However, rather than testing each value, you can use a binary search to find the appropriate font size. You know the size is somewhere between 1 and 100 (your upper range). using a binary search, each test sets the font size and checks the resulting layout. If the text is too large, then we search the lower half of the current range of possible values. If the font size fits, then we search the upper half. Your search will use at most 7 attempts (100 log base 2 rounded up), it will be exact, finding the largest size without going over, and it will be flexible if you need to add more requirements later, such as a mix of fonts or more stringent constraints on the layout.
I'm assuming you are using a text component that does line wrapping, and that you can set the maximum width to 400. So, you set the font size and it does the layout giving you back the required height, laying out text within the given width.
You can use hints to try to guide the algorithm to the result quicker, such as making your first guess close to the expected size, but text rendering is fast, that the performance increase may not be worth the implementation effort.
See Wikipedia - Binary Search Algorithm
I would do the following:
Assume you want W pixels wide text.
Pick an arbitrary size, say 10pt, and see what bounding box the text-string gets for that size. Lets say it gets N pixels wide.
Set the new size to 10pt * W/N, and repeat from step one, until you get within a reasonable threshold. (Hopefully it would work within one iteration.)
This relies on the fact that the width of the string, is roughly proportional to the size of the font.
I'd instantiate the Font at the largest desired size: say 72 for one inch glyphs at 72 dpi. Use TextLayout to get the bounds and scale using AffineTransform (direct) or AffineTransformOp (offscreen), while preserving the aspect ratio. Suitable RenderingHints help, too.