This is a method that returns awt fonts character as a bufferedimage:
private BufferedImage getCharImage(char ch) {
BufferedImage sizeImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D sizeGraphics = (Graphics2D) sizeImage.getGraphics();
if (antiAlias) {
sizeGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
FontMetrics fontMetrics = sizeGraphics.getFontMetrics(font);
int charwidth = fontMetrics.charWidth(ch);
if (charwidth <= 0) {
charwidth = 1;
}
int charheight = fontMetrics.getHeight();
if (charheight <= 0) {
charheight = fontSize;
}
BufferedImage charImage = new BufferedImage(charwidth, charheight, BufferedImage.TYPE_INT_ARGB);
Graphics2D charGraphics = (Graphics2D) charImage.getGraphics();
if (antiAlias) {
charGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
charGraphics.setFont(font);
charGraphics.setColor(Color.WHITE);
charGraphics.drawString(String.valueOf(ch), 0, fontMetrics.getAscent());
return charImage;
}
The problem is that i get incorrect width, and the characters don't fit to crated buffered images. If i would create the buffered image like this new BufferedImage(charwidth + 10, charheight, BufferedImage.TYPE_INT_ARGB); the characters would fit to images (that means that i get wrong font width). Why am i facing this prolem and how ca i fix it? I'm using arialbi.ttf (bold, italic).
This is how the font renders
Edit:
This is how i define the font variable:
font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
font = font.deriveFont(fontSize);
I think this is because the advance, or charWidth() doesn't have much to do with the actual width of the character as defined by the font.
Javadoc for FontMetrics.charWidth(char ch):
The advance is the distance from the leftmost point to the rightmost
point on the character's baseline
A character like "g" in Chancery Italic, below the baseline, extends left of the minimum x. Or "s" in the same font extends right of the maximum x, above the baseline. In a straighter font, like Arial Regular, the advance (baseline width) happens to be pretty close to the actual width of the character.
I'm not sure of a good solution besides adding arbitrary padding.
See also: http://docs.oracle.com/javase/tutorial/2d/text/measuringtext.html
I believe this is what is required to get the exact width of a string :
FontMetrics fontMetricesForLabel = graphics.getFontMetrics();
double width = fontMetricesForLabel.getStringBounds(label, graphics).getWidth();
Related
I have a requirements to create a web service that will generate user's icon based on their initials. Similar to this Android project but on the server side using Java.
The size of that image should be dynamic. I already have the code that will create a rectangle with two letters in the middle but it is not scaling the text.
Here is my code so far:
public BufferedImage getAbbreviationImage(int height, int width, String abbreviation) throws IOException {
int centerX = width/2;
int centerY = height/2;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.SCALE_SMOOTH);
Graphics2D g = bufferedImage.createGraphics();
Font font = new Font("Helvetica", Font.BOLD, 90);
g.setFont(font);
g.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
g.setColor(Color.decode("#3f404c"));
g.fillRect(0, 0, width, height);
// get the bounds of the string to draw.
FontMetrics fontMetrics = g.getFontMetrics();
Rectangle stringBounds = fontMetrics.getStringBounds(abbreviation, g).getBounds();
FontRenderContext renderContext = g.getFontRenderContext();
GlyphVector glyphVector = font.createGlyphVector(renderContext, abbreviation);
Rectangle visualBounds = glyphVector.getVisualBounds().getBounds();
// calculate the lower left point at which to draw the string. note that this we
// give the graphics context the y corridinate at which we want the baseline to
// be placed. use the visual bounds height to center on in conjuction with the
// position returned in the visual bounds. the vertical position given back in the
// visualBounds is a negative offset from the basline of the text.
int textX = centerX - stringBounds.width/2;
int textY = centerY - visualBounds.height/2 - visualBounds.y;
g.setColor(Color.WHITE);
g.drawString(abbreviation, textX, textY);
g.dispose();
return bufferedImage;
}
Is there any Java library that can do something like this already so that I don't have to write my own code. If not, then what would be the best approach to scale text based on the image size?
Credits:
Some of my code was take from HERE
You need to set the size of the font which you attach to the Graphics2D object. From the oracle docs:
public abstract void drawString(String str,
int x,
int y)
Renders the text of the specified String, using the current text
attribute state in the Graphics2D context
You should set the size of the font you use appropriately to match the dimensions of the rectangle. Something like this:
int lFontSize = 90 * (originalRectangleWidth / newRectangleWidth);
Font font = new Font("Helvetica", Font.BOLD, lFontSize );
where:
90 is the reference font size (this is what you are setting in the example)
originalRectangleWidth would be the size of the rectangle you use when the font looks good with size 90
newRectangleWidth would be the new rectangle width
References:
Graphics2D (oracle ref)
Font (oracle ref)
i have a java.util.List filled with java.io.File and i wan't to list them in a gui. To display them i wan't to show their name which is accessable by the method getName() of java.io.File. And i want to display their icon/image with which they are displayed for example on the desktop. To build this i am using the newest javaversion (Java8/1.8). So far i found one way to do get the image of any file which looks like this:
Icon icon = FileSystemView.getFileSystemView().getSystemIcon(pFile);
The problem about that is, that the returned icon is in a very small resolution(16x16) and i d like to display it in a bigger size. 80x80 would be perfect but doesn't have to be exactly it. 64x64 or smth like that would be fine, too. So i managed to resize the icon and stretch it but streatching 16x16 to 80x80 is not cool as you can imagine. There are to less pixels to get a good result.
I also found this tutorial but the first method shown in this tutorial doesn't work with Java8: http://www.rgagnon.com/javadetails/java-0439.html The method i am using right now is copied from there.
So is there any way to get a bigger sized icon like the one which is shown on the desktop of a specific file?
Thanks
Baschdi
private BufferedImage getBufferedImage(final File pFile)
throws FileNotFoundException {
Image icon = ShellFolder.getShellFolder(pFile).getIcon(true);
BufferedImage im = new BufferedImage(icon.getWidth(null),
icon.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = im.createGraphics();
g.drawImage(icon, 0, 0, null);
g.dispose();
int width = im.getWidth();
int height = im.getHeight();
System.out.println(width);
System.out.println(height);
final int maxHeigh = 79;
double scaleValue = 0;
if (height > width)
scaleValue = maxHeigh / height;
else
scaleValue = maxHeigh / width;
final int scaledWidth = (int) (im.getWidth() * scaleValue);
final int scaledHeigh = (int) (im.getHeight() * scaleValue);
BufferedImage resized = new BufferedImage(scaledWidth, scaledHeigh,
im.getType());
g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(im, 0, 0, scaledWidth, scaledHeigh, 0, 0, im.getWidth(),
im.getHeight(), null);
g.dispose();
return resized;
}
I imported the jdk7 and used the older function with ShellFolder. Works fine, even when running on java8. Thanks for the help :)
I have created a library called JIconExtractReloaded which can extract all icons sizes not only 32x32. Here is the link https://github.com/MrMarnic/JIconExtractReloaded.
Just write:
BufferedImage image = JIconExtract.getIconForFile(128,128,"C:\\Windows\\explorer.exe");
And you have the icon.
I'm looking for a way to infer a Java AWT Font size from a width. For example, I know I want to write 'hello world' within 100 pixels. I know I'm using the font "Times", in style Font.PLAIN, and I want to get the font size that fits the best with my given width of 100 pixels.
I know I could calculate it in a loop (something like while(font.getSize() < panel.getWidth()), but to be honest I don't find it very elegant.
You can get the rendered width and height of a string using the FontMetrics class (be sure to enable fractional font metrics in the Graphics2D instance to avoid rounding errors):
Graphics2D g = ...;
g.setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
Font font = Font.decode("Times New Roman");
String text = "Foo";
Rectangle2D r2d = g.getFontMetrics(font).getStringBounds(text, g);
Now, when you have the width of the text using a font with the default (or actually any) size, you can scale the font, so that the text will fit within a specified width, e.g. 100px:
font = font.deriveFont((float)(font.getSize2D() * 100/r2d.getWidth()));
Similarly, you may have to limit the font size, so that you don't exceed the available panel height.
To improve the appearance of the rendered text, you should also consider enabling antialiasing for text rendering and/or kerning support in the font:
g.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Map<TextAttribute, Object> atts = new HashMap<TextAttribute, Object>();
atts.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
font = font.deriveFont(atts);
Have a look at these two methods I am using. It is not elegant as you say, but it works.
private static int pickOptimalFontSize (Graphics2D g, String title, int width, int height) {
Rectangle2D rect = null;
int fontSize = 30; //initial value
do {
fontSize--;
Font font = Font("Arial", Font.PLAIN, fontSize);
rect = getStringBoundsRectangle2D(g, title, font);
} while (rect.getWidth() >= width || rect.getHeight() >= height);
return fontSize;
}
public static Rectangle2D getStringBoundsRectangle2D (Graphics g, String title, Font font) {
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
Rectangle2D rect = fm.getStringBounds(title, g);
return rect;
}
This is my function to resize images.
The quality is not photoshop but it's acceptable.
What's not acceptable is the behaviour on indexed png.
We expect that if we scale down an image with a 256 colors palette with a transparent index we would get a resized image with same transparency, but this it not the case.
So we did the resize on a new ARGB image and then we reduce it to 256 colors. The problem is how to "reintroduce" the transparent pixel index.
private static BufferedImage internalResize(BufferedImage source, int destWidth, int destHeight) {
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
double xScale = ((double) destWidth) / (double) sourceWidth;
double yScale = ((double) destHeight) / (double) sourceHeight;
Graphics2D g2d = null;
BufferedImage resizedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TRANSLUCENT);
log.debug("resizing image to w:" + destWidth + " h:" + destHeight);
try {
g2d = resizedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
AffineTransform at = AffineTransform.getScaleInstance(xScale, yScale);
g2d.drawRenderedImage(source, at);
} finally {
if (g2d != null)
g2d.dispose();
}
//doesn't keep the transparency
if (source.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
log.debug("reducing to color-indexed image");
BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED);
try {
Graphics g = indexedImage.createGraphics();
g.drawImage(resizedImage, 0, 0, null);
} finally {
if (g != null)
g.dispose();
}
System.err.println("source" + ((IndexColorModel) source.getColorModel()).getTransparentPixel()
+ " " + ((IndexColorModel) indexedImage.getColorModel()).getTransparentPixel());
return indexedImage;
}
return resizedImage;
}
Try changing
BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED);
to
BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED, (IndexColorModel) source.getColorModel());
Even if that specifically doesn't help you (which it might not if the resizing, for whatever reason, changes what specific color values are indexed), the fact that you can create a new BufferedImage with a given IndexColorModel will probably be quite useful for you.
http://download.oracle.com/javase/6/docs/api/java/awt/image/BufferedImage.html#BufferedImage%28int,%20int,%20int,%20java.awt.image.IndexColorModel%29
EDIT: Just noticed that your resizedImage constructor should probably use BufferedImage.TYPE_INT_ARGB rather than BufferedImage.TRANSLUCENT. Not sure if that will change how it works, but BufferedImage.TRANSLUCENT isn't supposed to be passed to that form of the constructor. http://download.oracle.com/javase/1,5.0/docs/api/java/awt/image/BufferedImage.html#BufferedImage%28int,%20int,%20int%29
Anyway, maybe try something like this:
DirectColorModel resizedModel = (DirectColorModel) resizedImage.getColorModel();
int numPixels = resizedImage.getWidth() * resizedImage.getHeight();
byte[numPixels] reds;
byte[numPixels] blues;
byte[numPixels] greens;
byte[numPixels] alphas;
int curIndex = 0;
int curPixel;
for (int i = 0; i < resizedImage.getWidth(); i++)
{
for (int j = 0; j < resizedImage.getHeight(); j++)
{
curPixel = resizedImage.getRGB(i, j);
reds[curIndex] = resizedModel.getRed(curPixel);
blues[curIndex]= resizedModel.getBlue(curPixel);
greens[curIndex] = resizedModel.getGreen(curPixel);
alphas[curIndex] = resizedModel.getAlpha(curPixel);
curIndex++;
}
}
BufferedImage indexedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_BYTE_INDEXED, new IndexColorModel(resizedModel.pixel_bits, numPixels, reds, blues, greens, alphas));
Don't know if this will actually work, though.
Indexed images with transparency are a hack. They only work under certain conditions and resizing isn't one of them.
An image with transparency doesn't just have fully opaque and fully transparent pixels. In particular at a irregularly shaped borders, there are many pixels with partial transparency. If you save it in a format with indexed colors where a single color is used for transparent pixels, you have to decide what color the background will have. All pixels with partial transparency are then blended between their color and the background color (according to their transparency) and become fully opaque. Only the fully transparent pixel are assigned the transparent pseudo color.
If such an image is displayed against a background with differnt color, an ugly border will become apparent. It's an artifact of the inadequate transparency handling.
When you resize the image, you introduce more artifacts. The color of a new pixels is usually blended from several neighboring pixels. If some are transparent and some are opaque, the result is a partially transparent pixel. When you save it, the partially transparent pixel is blended against the background color and becomes opaque. As a result, the opaque area (and the associated artifacts) grow with each resize (or most other image manipulations).
Whatever programming language or graphics library you use, the artifacts will grow and the result will become worse. I recommend you use a ARGB buffer and save the image as a non-indexed PNG file.
Java ImageIO correctly displays this black & white image http://www.jthink.net/jaikoz/scratch/black.gif but when I try and resize it using this code
public static BufferedImage resize2D(Image srcImage, int size)
{
int w = srcImage.getWidth(null);
int h = srcImage.getHeight(null);
// Determine the scaling required to get desired result.
float scaleW = (float) size / (float) w;
float scaleH = (float) size / (float) h;
MainWindow.logger.finest("Image Resizing to size:" + size + " w:" + w + ":h:" + h + ":scaleW:" + scaleW + ":scaleH" + scaleH);
//Create an image buffer in which to paint on, create as an opaque Rgb type image, it doesn't matter what type
//the original image is we want to convert to the best type for displaying on screen regardless
BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
// Set the scale.
AffineTransform tx = new AffineTransform();
tx.scale(scaleW, scaleH);
// Paint image.
Graphics2D g2d = bi.createGraphics();
g2d.setComposite(AlphaComposite.Src);
g2d.drawImage(srcImage, tx, null);
g2d.dispose();
return bi;
}
I just end up with a black image. Im trying to make the image smaller (a thumbnail) but even if I resize it larger for test purposes it still ends up as a black square.
Other images resize okay, anyone know what is the problem with the gif/and or Java Bug
Here is the string representation of the ColorModel of the linked image when loaded through ImageIO:
IndexColorModel: #pixelBits = 1 numComponents = 4 color space = java.awt.color.ICC_ColorSpace#1572e449 transparency = 2 transIndex = 1 has alpha = true isAlphaPre = false
If I understand this correctly, you have one bit per pixel, where a 0 bit is opaque black and a 1 bit is transparent. Your BufferedImage is initially all black, so drawing a mixture of black and transparent pixels onto it will have no effect.
Although you are using AlphaComposite.Src this will not help as the R/G/B values for the transparent palette entry read as zero (I am not sure whether this is encoded in the GIF or just the default in the JDK.)
You can work around it by:
Initializing the BufferedImage with all-white pixels
Using AlphaComposite.SrcOver
So the last part of your resize2D implementation would become:
// Paint image.
Graphics2D g2d = bi.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, size, size);
g2d.setComposite(AlphaComposite.SrcOver);
g2d.drawImage(srcImage, tx, null);
Try this:
BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
That makes it work. Of course, the question is why..?