Is it possible to render pixel-by-pixel identical result with Graphics2D.drawString on all java platforms with same font? I tried to render text without antialiasing, but results differs on different machines. I use font from project resources, so font is system-independent.
Example of results of same code with same font on two different PCs:
I'm used very small font size (9 px) for clarity.
import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
public class Test {
public static void main(String... args) throws FontFormatException, IOException {
int x = 0;
int y = 9;
int width = 80;
int height = 10;
float fontSizeInPixels = 9f;
String text = "PseudoText";
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
URL fontUrl = new URL(
"https://github.com/indvd00m/graphics2d-drawstring-test/blob/master/src/test/resources/fonts/DejaVuSansMono/DejaVuSansMono.ttf?raw=true");
Font font = Font.createFont(Font.TRUETYPE_FONT, fontUrl.openStream());
font = font.deriveFont(fontSizeInPixels);
Color fontColor = Color.BLACK;
Color backgroundColor = Color.WHITE;
graphics.setFont(font);
graphics.setColor(backgroundColor);
graphics.fillRect(0, 0, width, height);
graphics.setColor(fontColor);
graphics.drawString(text, x, y);
ImageIO.write(image, "png", new File("/tmp/test.png"));
}
}
I'm created project for test here:
https://github.com/indvd00m/graphics2d-drawstring-test
Failed build of this project:
https://travis-ci.org/indvd00m/graphics2d-drawstring-test/builds/178672466
Tests passed under openjdk6 and openjdk7 on linux but failed under oraclejdk7 and oraclejdk8 on linux and other OS and java versions.
Rendering text is extremely complex (evolving Opentype and Unicode specs, trying to fit small symbols on screens with too few pixels, work-arounding font bugs, etc).
It is so complex it is mostly system-dependent, with one remaining rendering engine per major OS. Those engines are continuously evolving to try to fix problems, support hardware changes and new spec revisions. They do not give the same results from system to system and OS version to OS versions. Sometimes apps like Oracle JDK add one more level of complexity by including their own rendering routines (for legacy compat, Open JDK uses the system rendering directly and gives better results on modern *nix systems).
You can try to erase some of the differences by rendering at very high pixel dimensions. That will remove all the lowdpi grid fitting black magic differences, and render close to the ideal high-quality paper printing. However the text will be very hard to read when scaled down to actual screen pixels.
In the meanwhile, differing interpretations on where and how put text pixels for better reading quality are a fact of life. You'd better not try to code anything that assumes otherwise. The text stack people will win, it's a very Darwinian process, the few text rendering engines that remain are survivors.
OK.. not sure this constitutes an 'answer' as such (more an experiment) but this is what I mean about scaling the text to fit the space. See comments in code.
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import javax.imageio.ImageIO;
public class TestFontWidthScaling {
private static Font font;
private static float fontSizeInPixels = 36f;
private static String text = "PseudoText";
private static int width = 320;
private static int height = 40;
private static BufferedImage scaleImageToFit() {
BufferedImage image = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
// graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
// we need line antialiasing here
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
FontRenderContext frc = graphics.getFontRenderContext();
// this is important for determining the *current* size of the
// text using this font.
Area area = new Area(font.
createGlyphVector(frc, text).
getOutline());
Rectangle2D bounds = area.getBounds2D();
double w = bounds.getWidth();
double h = bounds.getHeight();
double scaleW = width*.95 / w;
double scaleH = height*.9 / h;
AffineTransform scale = AffineTransform.getScaleInstance(scaleW, scaleH);
// we now have the shape of the text that will fill a fixed percentage
// of the width and height of the assigned space.
area = area.createTransformedArea(scale);
// now to center it
bounds = area.getBounds2D();
double moveX = bounds.getCenterX() - width/2;
double moveY = bounds.getCenterY() - height/2;
AffineTransform move = AffineTransform.getTranslateInstance(-moveX, -moveY);
// this should be both scaled to size AND centered in the space
area = area.createTransformedArea(move);
Color fontColor = Color.BLACK;
// changed to make image bounds more obvious on white BG
Color backgroundColor = Color.CYAN;
graphics.setFont(font);
graphics.setColor(backgroundColor);
graphics.fillRect(0, 0, width, height);
graphics.setColor(fontColor);
graphics.draw(area);
graphics.fill(area);
return image;
}
public static void main(String... args) throws FontFormatException, IOException {
int x = 0;
int y = 36;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
URL fontUrl = new URL(
"https://github.com/indvd00m/graphics2d-drawstring-test/blob/master/src/test/resources/fonts/DejaVuSansMono/DejaVuSansMono.ttf?raw=true");
font = Font.createFont(Font.TRUETYPE_FONT, fontUrl.openStream());
font = font.deriveFont(fontSizeInPixels);
Color fontColor = Color.BLACK;
Color backgroundColor = Color.WHITE;
graphics.setFont(font);
graphics.setColor(backgroundColor);
graphics.fillRect(0, 0, width, height);
graphics.setColor(fontColor);
graphics.drawString(text, x, y);
String userHome = System.getProperty("user.home");
File f = new File(userHome);
f = new File(f, "test.png");
ImageIO.write(image, "png", f);
Desktop.getDesktop().open(f);
BufferedImage scaledImage = scaleImageToFit();
f = new File(f.getParentFile(), "test-scaled.png");
ImageIO.write(scaledImage, "png", f);
Desktop.getDesktop().open(f);
}
}
Related
I've written a program to modify images.
First, I get the image, and get its drawing context like this:
BufferedImage image;
try {
image = ImageIO.read(inputFile);
} catch (IOException ioe) { /* exception handling ... */ }
Graphics g = image.createGraphics();
And then I modify the image like this:
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
g.setColor( /* calculate color ... */ );
g.fillRect(x, y, 1, 1);
}
}
After I've finished modifying the image, I save the image like this:
try {
ImageIO.write(image, "PNG", save.getSelectedFile());
} catch (IOException ioe) { /* exception handling ... */ }
Now most of the time this works just fine.
However, when I tried recoloring this texture
to this
I get this instead:
Inside the debugger, though, the Graphics's color is the shade of pink I want it to be.
The comments seem to suggest that the image the user opens might have some color limitations, and since I'm drawing to the same image I'm reading from, my program has to abide by these limitations. The example image seems to be pretty grayscale-y, and apparently its bit depth is 8 bit. So maybe the pink I'm drawing on it is converted to grayscale, because the image has to stay 8-bit?
As suggested in the comments, the main problem here indeed is the wrong color model. When you load the original image, and print some information about it...
BufferedImage image = ImageIO.read(
new URL("https://i.stack.imgur.com/pSUFR.png"));
System.out.println(image);
it will say
BufferedImage#5419f379: type = 13 IndexColorModel: #pixelBits = 8 numComponents = 3 color space = java.awt.color.ICC_ColorSpace#7dc7cbad transparency = 1 transIndex = -1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 128 height = 128 #numDataElements 1 dataOff[0] = 0
The IndexColorModel does not necessarily support all the colors, but only a subset of them. (Basically, the image supports only the colors that it "needs", which allows for a more compact storage).
The solution here is to convert the image into one that has the appropriate color model. A generic method for this is shown in the following example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
public class ImageColors
{
public static void main(String[] args) throws IOException
{
BufferedImage image = ImageIO.read(
new URL("https://i.stack.imgur.com/pSUFR.png"));
// This will show that the image has an IndexColorModel.
// This does not necessarily support all colors.
System.out.println(image);
// Convert the image to a generic ARGB image
image = convertToARGB(image);
// Now, the image has a DirectColorModel, supporting all colors
System.out.println(image);
Graphics2D g = image.createGraphics();
g.setColor(Color.PINK);
g.fillRect(50, 50, 50, 50);
g.dispose();
ImageIO.write(image, "PNG", new File("RightColors.png"));
}
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;
}
}
I am using Apache POI to convert pptx slide to image. In the pptx slide, I have Japanese text in GE Inspira font that is not available in my system (comment out ge.registerFont(font) to simulate that). The generated image shows the Japanese text in a default font (see image here). What font is that and where is this default font set?
When I register the font, the Japanese text appears as boxes (see image here). This is because GE Inspira font does not support Japanese characters. Is there a way to force POI to use the default font for Japanese text?
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;
import org.apache.poi.sl.usermodel.VerticalAlignment;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.apache.poi.xslf.usermodel.XSLFTextBox;
import org.apache.poi.xslf.usermodel.XSLFTextParagraph;
import org.apache.poi.xslf.usermodel.XSLFTextRun;
public class UnicodePPT {
public static void main(String[] args) throws Exception {
// create a sample pptx
XMLSlideShow ss = new XMLSlideShow();
Dimension pgsize = ss.getPageSize();
XSLFSlide slide = ss.createSlide();
XSLFTextBox tb = slide.createTextBox();
// tb.setShapeType(XSLFShapeType.HEART);
int shapeSize = 150;
tb.setAnchor(new Rectangle((int)(pgsize.getWidth() / 2 - shapeSize / 2), (int)(pgsize.getHeight()
/ 2
- shapeSize
/ 2), shapeSize, shapeSize));
tb.setLineWidth(2);
tb.setLineColor(Color.BLACK);
XSLFTextParagraph par = tb.addNewTextParagraph();
tb.setVerticalAlignment(VerticalAlignment.DISTRIBUTED);
par.setTextAlign(TextAlign.CENTER);
XSLFTextRun run = par.addNewTextRun();
run.setText("ゴミ箱");
run.setFontFamily("GE Inspira");
run.setFontSize(12.0);
// set the font
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
InputStream is = new FileInputStream("src/test/resources/GEInspRg.TTF");
Font font = Font.createFont(Font.TRUETYPE_FONT, is);
is.close();
ge.registerFont(font);
// render it
double zoom = 2; // magnify it by 2
AffineTransform at = new AffineTransform();
at.setToScale(zoom, zoom);
BufferedImage img = new BufferedImage((int)Math.ceil(pgsize.width * zoom),
(int)Math.ceil(pgsize.height * zoom), BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
graphics.setTransform(at);
graphics.setPaint(Color.white);
graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
slide.draw(graphics);
FileOutputStream fos = new FileOutputStream("src/test/resources/unicodeppt.png");
javax.imageio.ImageIO.write(img, "png", fos);
fos.close();
}
}
As mentioned in the comments, the Inspira GE font is not an unicode font, therefore a fallback is needed. If no specific fallback font is specified, Font.SANS_SERIF will be used. For the logical fonts like SANS_SERIF there is java internal fallback configuration. Unfortunately the automatically added Lucida fonts do not support Chinese (Simplified), Chinese (Traditional), Japanese, and Korean.
So you should provide a unicode font, e.g. Mona, Code2000 and Arial Unicode MS are a good picks - wikipedia has also good collection.
The mapping is provided as POI-specific rendering hint in the form of Map<String,String>, where the key is original font family and value the substitution font. You can also specify "*" as a key, to catch-all fonts. Beside the FONT_FALLBACK, there's also a FONT_MAP hint to map all the occurrences, i.e. not only the missing glyphs.
This will be available in POI 3.16-beta2 (ETA February 2017) or you temporarily use the trunk - another option is to provide your own DrawTextParagraph via a customized DrawFactory
To find out, if your font is capable of rendering your chars/glpyhs, you need to open it in the Windows character map tool or some other font tool like FontForge and check if there's a glyph at the unicode block.
#Test
public void unicodeRendering() throws Exception {
// create a sample pptx
XMLSlideShow ss = createSamplePPT();
Dimension pgsize = ss.getPageSize();
// set the font
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
for (String s : new String[]{"GEInspRg.ttf","mona.ttf"}) {
Font font = Font.createFont(Font.TRUETYPE_FONT, new File(s));
ge.registerFont(font);
}
Map<String,String> fallbackMap = new HashMap<String,String>();
fallbackMap.put("GE Inspira", "Mona");
// render it
double zoom = 2; // magnify it by 2
AffineTransform at = new AffineTransform();
at.setToScale(zoom, zoom);
BufferedImage img = new BufferedImage((int)Math.ceil(pgsize.width * zoom),
(int)Math.ceil(pgsize.height * zoom), BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
graphics.setRenderingHint(Drawable.FONT_FALLBACK, fallbackMap);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
graphics.setTransform(at);
graphics.setPaint(Color.white);
graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
ss.getSlides().get(0).draw(graphics);
javax.imageio.ImageIO.write(img, "png", new File("unicodeppt.png"));
ss.close();
}
private XMLSlideShow createSamplePPT() {
XMLSlideShow ss = new XMLSlideShow();
Dimension pgsize = ss.getPageSize();
XSLFSlide slide = ss.createSlide();
XSLFTextBox tb = slide.createTextBox();
// tb.setShapeType(XSLFShapeType.HEART);
int shapeSize = 150;
tb.setAnchor(new Rectangle((int)(pgsize.getWidth() / 2 - shapeSize / 2),
(int)(pgsize.getHeight() / 2 - shapeSize / 2), shapeSize, shapeSize));
tb.setLineWidth(2);
tb.setLineColor(Color.BLACK);
XSLFTextParagraph par = tb.addNewTextParagraph();
tb.setVerticalAlignment(VerticalAlignment.DISTRIBUTED);
par.setTextAlign(TextAlign.CENTER);
XSLFTextRun run = par.addNewTextRun();
run.setText("unicode ->\u30B4\u30DF\u7BB1<-");
run.setFontFamily("GE Inspira");
run.setFontSize(12.0);
return ss;
}
I have read through many related questions and other web resources for days, but I just can't find a solution.
I want to scale down very large images (e.g. 1300 x 27000 Pixel).
I cannot use a larger heap space for eclipse than 1024.
I rather don't want to use an external tool like JMagick since I want to export a single executable jar to run on other devices. Also from what I read I am not sure if even JMagick could do this scaling of very large images. Does anyone know?
Everything I tried so far results in "OutOfMemoryError: Java heap space"
I trieg e.g. coobird.thumbnailator or awt.Graphics2D, ...
Performance and quality are not the most important factors. Mainly I just want to be sure, that all sizes of images can be scaled down without running out of heap space.
So, is there a way to scale images? may be in small chunks so that the full image doesn't need to be loaded? Or any other way to do this?
As a workaround it would also be sufficient if I could just make a thumbnail of a smaller part of the image. But I guess cropping an large image will have the same problems as if scaling a large image?
Thanks and cheers!
[EDIT:]
With the Thumbnailator
Thumbnails.of(new File(".../20150601161616.png"))
.size(160, 160);
works for the particular picture, but
Thumbnails.of(new File(".../20150601161616.png"))
.size(160, 160)
.toFile(new File(".../20150601161616_t.png"));
runs out of memory.
I've never had to do that; but I would suggest loading the image in tiled pieces, scaling them down, printing the scaled-down version on the new BufferedImage, and then loading the next tile over the first.
Psuedocode (parameters may also be a little out of order):
Image finalImage;
Graphics2D g2D = finalImage.createGraphics();
for each yTile:
for each xTile:
Image orig = getImage(path, x, y, xWidth, yWidth);
g2D.drawImage(x * scaleFactor, y * scaleFactor, xWidth * scaleFactor, yWidth * scaleFactor, orig);
return orig;
Of course you could always do it the dreaded binary way; but this apparently addresses how to load only small chunks of an image:
Draw part of image to screen (without loading all to memory)
It seems that there are already a large number of prebuilt utilities for loading only part of a file.
I apologize for the somewhat scattered nature of my answer; you actually have me curious about this now and I'll be researching it further tonight. I'll try and make note of what I run into here. Good luck!
With your hints and questions I was able to write a class that actually does what I want. It might not scale all sizes, but works for very large images. The performance is very bad (10-15 Sec for an 1300 x 27000 png), but it works for my purposes.
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import net.coobird.thumbnailator.Thumbnails;
public class ImageManager {
private int tileHeight;
private String pathSubImgs;
/**
* #param args
*/
public static void main(String[] args) {
int tileHeightL = 2000;
String imageBasePath = "C:.../screenshots/";
String subImgsFolderName = "subImgs/";
String origImgName = "TestStep_319_20150601161652.png";
String outImgName = origImgName+"scaled.png";
ImageManager imgMngr = new ImageManager(tileHeightL,imageBasePath+subImgsFolderName);
if(imgMngr.scaleDown(imageBasePath+origImgName, imageBasePath+outImgName))
System.out.println("Scaled.");
else
System.out.println("Failed.");
}
/**
* #param origImgPath
* #param outImgPath
* #param tileHeight
* #param pathSubImgs
*/
public ImageManager(int tileHeight,
String pathSubImgs) {
super();
this.tileHeight = tileHeight;
this.pathSubImgs = pathSubImgs;
}
private boolean scaleDown(String origImgPath, String outImgPath){
try {
BufferedImage image = ImageIO.read(new File(origImgPath));
int origH = image.getHeight();
int origW = image.getWidth();
int tileRestHeight;
int yTiles = (int) Math.ceil(origH/tileHeight);
int tyleMod = origH%tileHeight;
for(int tile = 0; tile <= yTiles ; tile++){
if(tile == yTiles)
tileRestHeight = tyleMod;
else
tileRestHeight = tileHeight;
BufferedImage out = image.getSubimage(0, tile * tileHeight, origW, tileRestHeight);
ImageIO.write(out, "png", new File(pathSubImgs + tile + ".png"));
Thumbnails.of(new File(pathSubImgs + tile + ".png"))
.size(400, 400)
.toFile(new File(pathSubImgs + tile + ".png"));
}
image = ImageIO.read(new File(pathSubImgs + 0 + ".png"));
BufferedImage img2;
for(int tile = 1; tile <= yTiles ; tile++){
if(tile == yTiles)
tileRestHeight = tyleMod;
else
tileRestHeight = tileHeight;
img2 = ImageIO.read(new File(pathSubImgs + tile + ".png"));
image = joinBufferedImage(image, img2);
}
ImageIO.write(image, "png", new File(outImgPath));
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static BufferedImage joinBufferedImage(BufferedImage img1,BufferedImage img2) {
//do some calculate first
int height = img1.getHeight()+img2.getHeight();
int width = Math.max(img1.getWidth(),img2.getWidth());
//create a new buffer and draw two image into the new image
BufferedImage newImage = new BufferedImage(width,height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = newImage.createGraphics();
Color oldColor = g2.getColor();
//fill background
g2.setPaint(Color.WHITE);
g2.fillRect(0, 0, width, height);
//draw image
g2.setColor(oldColor);
g2.drawImage(img1, null, 0, 0);
g2.drawImage(img2, null, 0, img1.getHeight());
g2.dispose();
return newImage;
}
}
I have a question concerning printing additional information on barcodes. I am using http://barbecue.sourceforge.net/ to create my barcodes.
After I created my barcodes I want to add some additional information. At the moment i do this with the following way! For example:
Graphics2D g2d5 = container4Barcode.createGraphics();
g2d5.setBackground(Color.WHITE);
g2d5.clearRect(0, 33, 200, 200);
g2d5.setColor(Color.BLACK);
g2d5.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d5.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
g2d5.setFont(new Font("Arial", Font.PLAIN, 8));
g2d5.drawString(barcode, 8, 40);
g2d5.drawString(generateRandomNumber(ekPreis), 57, 40);
String datumResult = datum;
g2d5.drawString(location, 98, 40);
g2d5.drawString(datum.substring(2), 114, 40);
g2d5.dispose();
The output is in a pdf the following:
As you can see is the quality of my text (above and under the barcode) is really bad ... How can I increase the quality of the text to make the text more smoother and not that pixelated?!
(When I print my barcodes, the barcodes look very pixelated ...)
Any tips?
UPDATE:
So, I added here the a picture of my latest outcome ... When I print out these barcodes they look horrible! So here is the code what I did:
Graphics2D g2d6 = container4Barcode.createGraphics();
g2d6.setColor(Color.black);
g2d6.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d6.setFont(new Font("Verdana", Font.BOLD, 7));
g2d6.drawString("FLORETT", 9, 20);
g2d6.drawString("50-521-60", 57, 20);
Graphics2D g2d4 = container4Barcode.createGraphics();
g2d4.setColor(Color.black);
g2d4.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d4.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d4.setFont(new Font("Verdana", Font.BOLD, 11));
g2d4.drawString("SSYYS", 105, 19);
g2d4.dispose();
With that Code I get the best results! Of course I played with "Metrics, AA_GASP, LCS_HRGB, different fonts (Verdana is the best in my opinion) ..." and a lot more, but some of them I couldn't use, because then my barcode got blurred! So actioally I am forcing the problem that I am unable to improve the quality of my text-quality of the drawstring from graphics2d!
So, I want to ask if there is a possibility to let the "SSYYS" (Font Size 11) and the "FLORETT" (Font Size 7) look much nicer! Is there a possibility in JAVA to draw "smooth" text on an image with a font size less than "12" ? Is there a workaround to to that ? As you can see in the picture the letters "S and Y" look very awful...
2nd Update:
Some Example code so far... Please be sure that the following folder exists: C:\TestBarcodes\
Hope I reduced my code to the minimum that you can imagine what my problem is...
package generator;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import net.sourceforge.barbecue.Barcode;
import net.sourceforge.barbecue.BarcodeException;
import net.sourceforge.barbecue.BarcodeFactory;
import net.sourceforge.barbecue.output.OutputException;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
public class BarcodeGen {
// sets the picWidth
private static int picWidth = 149;
// sets the picHeigth
private static int picHeigth = 60;
public static void main(String[] args)
throws BarcodeException, OutputException, COSVisitorException, IOException {
generateBarcode("11138500");
}
public static void generateBarcode(String barcode)
throws IOException, COSVisitorException, BarcodeException, OutputException {
Barcode barcode2 = BarcodeFactory.createCode39(barcode, false);
int gw = barcode2.getWidth();
// change this to suit if you want higher, default 50
// barcode2.setBarWidth(50);
// this sets DPI
barcode2.setResolution(100);
// barcode2.setFont(font);
int gh = barcode2.getHeight();
// change this if you want a coloured background
// image = new BufferedImage(t, s, BufferedImage.TYPE_INT_RGB)
BufferedImage image = new BufferedImage(gw, gh, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
// default is black so draw a white box first
// change type to INT_RGB if you want a coloured background
g2.setColor(Color.white);
g2.fillRect(0, 0, gw, gh);
barcode2.draw(g2, 0, 0);
// CREATE ADDITIONAL INFORMATION ON BARCODE
BufferedImage container4Barcode = new BufferedImage(
picWidth, picHeigth, image.getType());
Graphics2D g2d = container4Barcode.createGraphics();
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, picWidth, picHeigth);
g2d.setColor(Color.black);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
g2d.drawImage(image, 8, 21, 130, 18, null);
g2d.dispose();
Graphics2D g2d6 = container4Barcode.createGraphics();
g2d6.setColor(Color.black);
g2d6.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d6.setFont(new Font("Verdana", Font.BOLD, 7));
g2d6.drawString("FLORETT", 9, 20);
g2d6.drawString("50-521-60", 57, 20);
Graphics2D g2d4 = container4Barcode.createGraphics();
g2d4.setColor(Color.black);
g2d4.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d4.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d4.setFont(new Font("Verdana", Font.BOLD, 11));
g2d4.drawString("SSYYS", 105, 19);
g2d4.dispose();
// PRINT PDF
int ver = 782;
PDDocument doc = new PDDocument();
PDPage page = new PDPage(PDPage.PAGE_SIZE_A4);
doc.addPage(page);
PDXObjectImage image2 = new PDJpeg(doc, container4Barcode);
PDPageContentStream content = new PDPageContentStream(doc, page);
content.drawImage(image2, 5, ver);
content.close();
doc.save(new FileOutputStream("C:\\TestBarcodes\\barcode.pdf"));
// opens the pdf file
Process p = Runtime
.getRuntime()
.exec("rundll32 url.dll,FileProtocolHandler C:\\TestBarcodes\\barcode.pdf");
try {
p.waitFor();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
If someone wants to use pixel images in such cases, and not vector, then image should be upscaled for better printing quality:
static final int PIXELS_PER_POINT = 4; // 4x
Then define all dimensions in points, not in pixels:
// Image size in points
static final int IMAGE_WIDTH = 150;
static final int IMAGE_HEIGHT = 60;
// Font size in points
static final int FONT_SIZE = 11;
Now, when do any drawing, always use points converted to pixels:
static int toPixels(int value) {
return value * PIXELS_PER_POINT;
}
BufferedImage draw() {
BufferedImage image =
new BufferedImage(toPixels(IMAGE_WIDTH), toPixels(IMAGE_HEIGHT), TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// <graphics init code goes here>
Font font = new Font("Arial", Font.PLAIN, toPixels(FONT_SIZE));
g.setFont(font);
g.drawString("Grapes", toPixels(5), toPixels(40)); // coordinates are in points
g.dispose()
return image;
}
So, with this approach you can operate with 'standard' dimentions. This approach works quite well for me for low- and medium complexity drawings.
You can go further and convert PIXELS_PER_POINT to a parameter: use 1x for images on web-pages with ordinary display, 2x for Retina displays and 4x for printing!
To fix the jagged edges in text or shapes in Graphics2d you need to set RenderingHint.
Graphics2D g2d = bufferedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Explanation
A typical antialiasing algorithm works by blending the existing colors of the pixels along the boundary of a shape with the requested fill paint according to the estimated partial pixel coverage of the shape.
Text antialiasing hint key. The TEXT_ANTIALIASING hint can control the use of antialiasing algorithms for text independently of the choice used for shape rendering. Often an application may want to use antialiasing for text only and not for other shapes. Additionally, the algorithms for reducing the aliasing artifacts for text are often more sophisticated than those that have been developed for general rendering so this hint key provides additional values which can control the choices of some of those text-specific algorithms. If left in the DEFAULT state, this hint will generally defer to the value of the regular KEY_ANTIALIASING hint key.
Below is a small code which takes input of File containing image and then tilts it by an angle. Now the problem is that: the output file has a lower resolution when compared to the input one. In my case input file was of size 5.5 MB and the output file was of 1.1 MB.
Why is it?
/**
*
* #param angle Angle to be rotate clockwise. Ex: Math.PI/2, -Math.PI/4
*/
private static void TurnImageByAngle(File image, double angle)
{
BufferedImage original = null;
try {
original = ImageIO.read(image);
GraphicsConfiguration gc = getDefaultConfiguration();
BufferedImage rotated1 = tilt(original, angle, gc);
//write iamge
ImageIO.write(rotated1, getFileExtension(image.getName()), new File("temp"+" "+angle+"."+getFileExtension(image.getName())));
} catch (IOException ex) {
Logger.getLogger(RotateImage2.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static GraphicsConfiguration getDefaultConfiguration() {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
return gd.getDefaultConfiguration();
}
public static BufferedImage tilt(BufferedImage image, double angle, GraphicsConfiguration gc) {
double sin = Math.abs(Math.sin(angle)), cos = Math.abs(Math.cos(angle));
int w = image.getWidth(), h = image.getHeight();
int neww = (int)Math.floor(w*cos+h*sin), newh = (int)Math.floor(h*cos+w*sin);
int transparency = image.getColorModel().getTransparency();
BufferedImage result = gc.createCompatibleImage(neww, newh, transparency);
Graphics2D g = result.createGraphics();
g.translate((neww-w)/2, (newh-h)/2);
g.rotate(angle, w/2, h/2);
g.drawRenderedImage(image, null);
return result;
}
Thats no surprise if you look at the code (Copy&Paste without understanding what the Code does has its drawbacks). The tilt()-Method makes extra effort (in its 3rd line) to make the image properly sized.
If you think about it, you cant expect the image to stay the same size.
Potentially, the resulting image may not have the same color model as the original
gc.createCompatibleImage(...)
Is creating a BufferedImage whose color model is compatible with device that the GraphicsConfiguration is associated. This may potentially reduce the size of the image.
ImageIO may also be also be applying a different compression algorithm from the original