i have an application from which i want to print an image. The image is loaded as a BufferedImage object. The problem is, when i print the image (to the postscript or to the pdf file), the quality is really poor.
When i'm using some other tools (basically any picture viewer application which can print the image) the result is significantly better.
I know there can be some problems with the DPI vs resolution but i'm not exactly sure how to compute the correct values for printing.
I tried to google and tried some methods, but nothing seems to work as i expected.
Basicaly i just want to print an image (in resolution let's say 3000x2000) to a printer (with DPI for example 600x600).
This is how i create the print job:
PrintRequestAttributeSet printAttributes = new HashPrintRequestAttributeSet();
printAttributes.add(PrintQuality.HIGH);
printAttributes.add(new PrinterResolution(600, 600 PrinterResolution.DPI));
printAttributes.add(new Destination(URI.create("file:/tmp/test.ps")));
PageFormat pf = printerJob.defaultPage();
Paper paper = pf.getPaper();
double xMargin = 0.0;
double yMargin = 0.0;
paper.setImageableArea(xMargin, yMargin, paper.getWidth() - 2 * xMargin, paper.getHeight() - 2 * yMargin);
pf.setPaper(paper);
// create new Printable for the specified image
printerJob.setPrintable(PrintableImage.get(image), pf)
if (printerJob.printDialog(printAttributes)) {
printerJob.print(printAttributes);
}
Where image is BufferedImage and PrintableImage.get returns new instance which implements Printable
Then the actual print is doing this way (i let the commented code which i tried but didn't work for me)
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
if (image == null)
throw new PrinterException("no image specified to be printed");
// We have only one page, and 'page' is zero-based
if (pageIndex > 0) {
return NO_SUCH_PAGE;
}
// tranlate the coordinates (according to the orientations, margins, etc)
Graphics2D printerGraphics = (Graphics2D) graphics;
//g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
//g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
printerGraphics.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// THIS IS A TEST - javax.printing api uses 72 DPI, but we have 600DPI set for the printer
//AffineTransform at = printerGraphics.getTransform();
//printerGraphics.scale((double)72 / (double)600, (double)72 / (double)600);
//printerGraphics.drawRenderedImage(image, null);
//printerGraphics.setTransform(at);
//if(printerGraphics != null)
//return PAGE_EXISTS;
double scale = 72.0 / 600.0;
Dimension pictureSize = new Dimension((int)Math.round(image.getWidth() / scale), (int) Math.round(image.getHeight() / scale));
// center the image horizontaly and verticaly on the page
int xMargin = (int) ((pageFormat.getImageableWidth() - image.getWidth()) / 2);
int yMargin = (int) ((pageFormat.getImageableHeight() - image.getHeight()) / 2);
xMargin = yMargin = 0;
System.out.println(String.format("page size [%.2f x %.2f], picture size [%.2f x %.2f], margins [%d x %d]", pageFormat.getImageableWidth(), pageFormat.getImageableHeight(), pictureSize.getWidth(), pictureSize.getHeight(), xMargin, yMargin));
printerGraphics.drawImage(image, xMargin, yMargin, (int)pageFormat.getWidth(), (int)pageFormat.getHeight(), null);
//printerGraphics.drawImage(image, 0, 0, null);
//printerGraphics.drawImage(image, xMargin, yMargin, pictureSize.width, pictureSize.height, null);
//printerGraphics.drawImage(image, xMargin, yMargin, (int) pageFormat.getImageableWidth(), (int) pageFormat.getImageableHeight(), 0, 0, pictureSize.width, pictureSize.height, null);
//printerGraphics.drawImage(image, 0, 0, (int) pageFormat.getWidth() - xMargin, (int) pageFormat.getHeight() - yMargin, 0, 0, pictureSize.width, pictureSize.height, null);
return PAGE_EXISTS;
}
Does anybody solves the same problem?
Any help would be appreciated.
Thanks
Matej
This is how I did it. It works smoothly. Note that this code snippet doesn't center the image.
public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException {
if (pageIndex > 0) {
return (NO_SUCH_PAGE);
} else {
double pageHeight = pageFormat.getImageableHeight(), pageWidth = pageFormat.getImageableWidth();
Graphics2D g2d = (Graphics2D) g;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
if (pageHeight < image.getHeight() || pageWidth < image.getWidth()) {
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(image, 0, 0, (int) pageWidth, (int) pageHeight - textSize, null);
} else {
g2d.drawImage(image, 0, 0, null);
}
g2d.dispose();
return (PAGE_EXISTS);
}
}
#Viktor Fonic
Thank you for this, I was searching a lot to do this!
You're solution works perfectly, but has a small error,
textSize was not declared, so:
int textSize = (int) (pageHeight - image.getHeight()*pageWidth/image.getWidth());
Related
I am trying to calculate image center(to add water marks)following - add-water-to-image and the results shows negative x, for some of the images, here is the math part:
int centerX = (sourceImage.getWidth() - (int) rect.getWidth()) / 2;
int centerY = sourceImage.getHeight() / 2;
and the entire function:
public void addTextWatermark(String text, File sourceImageFile, File destImageFile, int textSize) {
try {
BufferedImage sourceImage = ImageIO.read(sourceImageFile);
Graphics2D g2d = (Graphics2D) sourceImage.getGraphics();
// initializes necessary graphic properties
AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f);
g2d.setComposite(alphaChannel);
g2d.setColor(Color.BLACK.darker());
//Font(fontName, fontStyle, foneSize)
g2d.setFont(new java.awt.Font("Arial", Font.BOLD, textSize));
FontMetrics fontMetrics = g2d.getFontMetrics();
//text - input text , g2d - Graphics2D
Rectangle2D rect = fontMetrics.getStringBounds(text, g2d);
// calculates the coordinate where the String is painted
int centerX = (sourceImage.getWidth() - (int) rect.getWidth()) / 2;
int centerY = sourceImage.getHeight() / 2;
// paints the textual watermark
g2d.drawString(text, centerX, centerY);
ImageIO.write(sourceImage, "png", destImageFile);
g2d.dispose();
} catch (IOException ex) {
System.out.println(ex.getMessage().toString());
}
}
1-Is there a way to ensure the math will work for all images?
2-Is there a diffrence between jpg and png in this calclation?
Thanks.
edit
The image sizes that was causeing it were:
1-to big(3000*3000).
2-to small(60*60).
With textSize(g2d.setFont(new java.awt.Font("Arial", Font.BOLD, textSize));) - 32 or less.
So I found 2 ways to handle this issue:
1- if x or y are smaller then 0 I have resize the image using resize-image
2- if the watermark is to small just increase text size - g2d.setFont(new java.awt.Font("Arial", Font.BOLD, textSize));
Thanks for all the help #jon Skeet.
I am just wondering how to rotate a rectangle image with paintComponent() method of JLabel component and set its new width and height correctly?
I tried to rotate (see attached image) and the image scale becomes bigger but the JLabel scale keeps the same what makes image be out of JLabel bounds or something :S So my question is how to set image new width and height to component dynamically in a more optimal way?
+1 to MadProgrammers comment and link.
Using the method from link (edited slightly to omit use of GraphicsConfiguration, drawRenderImage(..) and changed variable names):
//https://stackoverflow.com/questions/4156518/rotate-an-image-in-java
public static BufferedImage createTransformedImage(BufferedImage image, double angle) {
double sin = Math.abs(Math.sin(angle));
double cos = Math.abs(Math.cos(angle));
int originalWidth = image.getWidth();
int originalHeight = image.getHeight();
int newWidth = (int) Math.floor(originalWidth * cos + originalHeight * sin);
int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
BufferedImage rotatedBI = new BufferedImage(newWidth, newHeight, BufferedImage.TRANSLUCENT);
Graphics2D g2d = rotatedBI.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate((newWidth - originalWidth) / 2, (newHeight - originalHeight) / 2);
g2d.rotate(angle, originalWidth / 2, originalHeight / 2);
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return rotatedBI;
}
Here is an example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RotateImage {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MyRotatableImage(createImage(), -45));
frame.pack();
frame.setVisible(true);
}
});
}
public static BufferedImage createImage() {
BufferedImage img = new BufferedImage(100, 50, BufferedImage.TRANSLUCENT);
Graphics2D g2d = img.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, img.getWidth(), img.getHeight());
g2d.setColor(Color.BLACK);
g2d.setFont(new Font("Calibri", Font.BOLD, 20));
FontMetrics fm = g2d.getFontMetrics();
String text = "Hello world";
int textWidth = fm.stringWidth(text);
g2d.drawString(text, (img.getWidth() / 2) - textWidth / 2, img.getHeight() / 2);
g2d.dispose();
return img;
}
}
class MyRotatableImage extends JPanel {
private BufferedImage transformedImage;
public MyRotatableImage(BufferedImage img, int angle) {
transformedImage = createTransformedImage(img, angle);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(transformedImage, 0, 0, null);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(transformedImage.getWidth(), transformedImage.getHeight());
}
//https://stackoverflow.com/questions/4156518/rotate-an-image-in-java
public static BufferedImage createTransformedImage(BufferedImage image, double angle) {
double sin = Math.abs(Math.sin(angle));
double cos = Math.abs(Math.cos(angle));
int originalWidth = image.getWidth();
int originalHeight = image.getHeight();
int newWidth = (int) Math.floor(originalWidth * cos + originalHeight * sin);
int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
BufferedImage rotatedBI = new BufferedImage(newWidth, newHeight, BufferedImage.TRANSLUCENT);
Graphics2D g2d = rotatedBI.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate((newWidth - originalWidth) / 2, (newHeight - originalHeight) / 2);
g2d.rotate(angle, originalWidth / 2, originalHeight / 2);
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return rotatedBI;
}
}
Reference:
Rotate an image in java
OK, so you want the JLabel to keep its dimensions and resize the image instead.
You're probably already using an affine transform anyways, so you can use some trigonometry and min/max to find the AABB of the rotated image. Scale appropriately (I assume you're already using affine transforms to do the rotation)
Edit:
AABB = Axis Aligned Bounding Box, its sides correspond to the min/max x,y coords of the rotated image. Just a more concise way of saying things.
OK I tried the sample David Kroukamp showed me. Thanks, David, you pointed me to right direction. I think it is a really good snippet to lean back on.
public static BufferedImage rotateImage(Image image, int angle)
{
double sin = Math.abs(Math.sin(angle));
double cos = Math.abs(Math.cos(angle));
int originalWidth = image.getWidth(null);
int originalHeight = image.getHeight(null);
int newWidth = (int) Math.floor(originalWidth * cos + originalHeight * sin);
int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
BufferedImage rotatedBI = new BufferedImage(newWidth, newHeight, BufferedImage.TRANSLUCENT);
Graphics2D g2d = rotatedBI.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate((newWidth - originalWidth) / 2, (newHeight - originalHeight) / 2);
g2d.rotate(angle, originalWidth / 2, originalHeight / 2);
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return rotatedBI;
}
David Kroukamp's result image preview
I tested the snippet and the result image + its component's scale were really dynamic changed.
But the result component width I've got has been always a little bit wider than it's inner image was :S
For example here is my version of an image rotated in 45 degrees angle
... As I could find (on blue background) its width is not totally fit to surround white image ... Actually I was looking for some kind of standard Java Image Processing rotate solution but I couldn't find it :( so I had to dig deeper to the problem and at least figure out how to solve the 'wider effect' and avoid create new BufferedImage object every re-paint...
OK, as a reault of my research, I tried to write some kind of my rotate code adaptation. Here it is :
>tested
public static void rotateImage(Graphics g, Image image,int tilt,JComponent component)
{
// create the transform, note that the transformations happen
// in reversed order (so check them backwards)
AffineTransform at = new AffineTransform();
//5. modify component scale ...
double sin = Math.abs(Math.sin(Math.toRadians(tilt)));
double cos = Math.abs(Math.cos(Math.toRadians(tilt)));
int w=image.getWidth(null);
int h=image.getHeight(null);
int newW=(int) Math.floor(w * cos + h * sin);
int newH=(int) Math.floor(h * cos + w * sin);
component.setSize(newW, newH);
int width=component.getWidth();
int height=component.getHeight();
// 4. translate it to the center of the component
at.translate(width / 2, height / 2);
// 3. do the actual rotation
at.rotate(Math.toRadians(tilt));
// 2. just a scale because this image is big
// at.scale(1, 1);
// 1. translate the object so that you rotate it around the
// center (easier :))
at.translate(-image.getWidth(null)/2, -image.getHeight(null)/2);
// draw the image
((Graphics2D) g).drawImage(image, at, null);
}
... so the result image rotated on -30 degrees tilt looks like this
I dearly hope the tip saves ones day :)
Thank you all for help
I am trying to rotate an instance of a BufferImage named pic when I try this it resizes and skews and crops the image, any advice to get it to work properly
public void rotate(double rads){
AffineTransform tx = new AffineTransform();
tx.rotate(rads,pic.getWidth()/2,pic.getHeight()/2);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
pic = op.filter(pic, null);
}
When I have it rotate 90˚ it works fine so I'm wondering if the problem is that it is the shape of the image?
For use with AffineTransform, you can square an image using something like this:
private BufferedImage getImage(String name) {
BufferedImage image;
try {
image = ImageIO.read(new File(name));
} catch (IOException ioe) {
return errorImage;
}
int w = image.getWidth();
int h = image.getHeight();
int max = Math.max(w, h);
max = (int) Math.sqrt(2 * max * max);
BufferedImage square = new BufferedImage(
max, max, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = square.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(image, (max - w) / 2, (max - h) / 2, null);
g2d.dispose();
return square;
}
I have snippet of code that I am using for the purpose of resizing an image to a curtain size (I want to change the resolution to something like 200 dpi). Basically the reason I need it is because I want to display the image that the user have picked (somewhat large) and then if the user approves I want to display the same image in a different place but using a smaller resolution. Unfortunately, if I give it a large image nothing appears on the screen. Also, if I change
imageLabel.setIcon(newIcon);
to
imageLabel.setIcon(icon);
I get the image to display but not in the correct resolution that's how I know that I have a problem inside this snipper of code and not somewhere else.
Image img = icon.getImage();
BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.createGraphics();
boolean myBool = g.drawImage(img, 0, 0, 100, 100, null);
System.out.println(myBool);
ImageIcon newIcon = new ImageIcon(bi);
imageLabel.setIcon(newIcon);
submitText.setText(currentImagePath);
imageThirdPanel.add(imageLabel);
You don't really have to care about the details of scaling images. The Image class has already a method getScaledInstance(int width, int height, int hints) designed for this purpose.
Java documentation says:
Creates a scaled version of this image. A new Image object is returned
which will render the image at the specified width and height by
default. The new Image object may be loaded asynchronously even if the
original source image has already been loaded completely. If either
the width or height is a negative number then a value is substituted
to maintain the aspect ratio of the original image dimensions.
And you can use it like this:
// Scale Down the original image fast
Image scaledImage = imageToScale.getScaledInstance(newWidth, newHighth, Image.SCALE_FAST);
// Repaint this component
repaint();
Check this for a complete example.
Here is my solution:
private BufferedImage resizeImage(BufferedImage originalImage, int width, int height, int type) throws IOException {
BufferedImage resizedImage = new BufferedImage(width, height, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, width, height, null);
g.dispose();
return resizedImage;
}
Try this CODE to resize image :
public static Image scaleImage(Image original, int newWidth, int newHeight) {
//do nothing if new and old resolutions are same
if (original.getWidth() == newWidth && original.getHeight() == newHeight) {
return original;
}
int[] rawInput = new int[original.getHeight() * original.getWidth()];
original.getRGB(rawInput, 0, original.getWidth(), 0, 0, original.getWidth(), original.getHeight());
int[] rawOutput = new int[newWidth * newHeight];
// YD compensates for the x loop by subtracting the width back out
int YD = (original.getHeight() / newHeight) * original.getWidth() - original.getWidth();
int YR = original.getHeight() % newHeight;
int XD = original.getWidth() / newWidth;
int XR = original.getWidth() % newWidth;
int outOffset = 0;
int inOffset = 0;
for (int y = newHeight, YE = 0; y > 0; y--) {
for (int x = newWidth, XE = 0; x > 0; x--) {
rawOutput[outOffset++] = rawInput[inOffset];
inOffset += XD;
XE += XR;
if (XE >= newWidth) {
XE -= newWidth;
inOffset++;
}
}
inOffset += YD;
YE += YR;
if (YE >= newHeight) {
YE -= newHeight;
inOffset += original.getWidth();
}
}
return Image.createRGBImage(rawOutput, newWidth, newHeight, false);
}
Another example is given here :
2D-Graphics/LoadImageandscaleit.htm">http://www.java2s.com/Tutorial/Java/0261_2D-Graphics/LoadImageandscaleit.htm
http://www.java2s.com/Code/JavaAPI/java.awt/ImagegetScaledInstanceintwidthintheightinthints.htm
I need to perform java image crop and resize without an X server.
I tried several methods.
the first method below works, but outputs a fairly ugly resized image (probably using nearest neighbor algorithm for the resize:
static BufferedImage createResizedCopy(Image originalImage, int scaledWidth, int scaledHeight, boolean preserveAlpha)
{
int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
Graphics2D g = scaledBI.createGraphics();
if (preserveAlpha)
{
g.setComposite(AlphaComposite.Src);
}
g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
g.dispose();
return scaledBI;
}
So I decided to use bicubic resize, which gives better results:
public static BufferedImage createResizedCopy(BufferedImage source, int destWidth, int destHeight, Object interpolation)
{
if (source == null) throw new NullPointerException("source image is NULL!");
if (destWidth <= 0 && destHeight <= 0) throw new IllegalArgumentException("destination width & height are both <=0!");
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
double xScale = ((double) destWidth) / (double) sourceWidth;
double yScale = ((double) destHeight) / (double) sourceHeight;
if (destWidth <= 0)
{
xScale = yScale;
destWidth = (int) Math.rint(xScale * sourceWidth);
}
if (destHeight <= 0)
{
yScale = xScale;
destHeight = (int) Math.rint(yScale * sourceHeight);
}
GraphicsConfiguration gc = getDefaultConfiguration();
BufferedImage result = gc.createCompatibleImage(destWidth, destHeight, source.getColorModel().getTransparency());
Graphics2D g2d = null;
try
{
g2d = result.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation);
AffineTransform at = AffineTransform.getScaleInstance(xScale, yScale);
g2d.drawRenderedImage(source, at);
}
finally
{
if (g2d != null) g2d.dispose();
}
return result;
}
public static GraphicsConfiguration getDefaultConfiguration()
{
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
return gd.getDefaultConfiguration();
}
This worked fine until I tried to put it on the server, and at this point I bumped into java.awt.HeadlessException.
my attempts to play with java.awt.headless=true failed.
So, here is the question:
How do I resize and crop and image in Java without an X server, using a bicubic interpolation algorithm?
Answer:
Using the code from Bozho comment, I created this function which does the trick (interpolation should be RenderingHints.VALUE_INTERPOLATION_*).
public static BufferedImage createResizedCopy(BufferedImage source, int destWidth, int destHeight, Object interpolation)
{
BufferedImage bicubic = new BufferedImage(destWidth, destHeight, source.getType());
Graphics2D bg = bicubic.createGraphics();
bg.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation);
float sx = (float)destWidth / source.getWidth();
float sy = (float)destHeight / source.getHeight();
bg.scale(sx, sy);
bg.drawImage(source, 0, 0, null);
bg.dispose();
return bicubic;
}
Check this code. Also check if Image.getScaledInstance(..) (with "smooth" scaling) doesn't solve the problem. And finally, take a look at the java-image-scaling-library.