31

I have an application that I want to export high-resolution (or rather, high pixel density?) images for printing - for example, I want images that print at 250 dots per inch (DPI), instead of the default, which I understand to be 72 DPI.

I'm using a BufferedImage with a Graphics2D object to draw the image, then ImageIO.write() to save the image.

Any idea how I can set the DPI?

Mark Biek
  • 146,731
  • 54
  • 156
  • 201
David Koelle
  • 20,726
  • 23
  • 93
  • 130

3 Answers3

33

Kurt's answer showed the way, still it took me quite some time to get it run, so here is the code that sets DPI when saving a PNG. There is a lot to do to get the proper writers and such...

 private BufferedImage gridImage;
 ...

 private void saveGridImage(File output) throws IOException {
    output.delete();

    final String formatName = "png";

    for (Iterator<ImageWriter> iw = ImageIO.getImageWritersByFormatName(formatName); iw.hasNext();) {
       ImageWriter writer = iw.next();
       ImageWriteParam writeParam = writer.getDefaultWriteParam();
       ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
       IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);
       if (metadata.isReadOnly() || !metadata.isStandardMetadataFormatSupported()) {
          continue;
       }

       setDPI(metadata);

       final ImageOutputStream stream = ImageIO.createImageOutputStream(output);
       try {
          writer.setOutput(stream);
          writer.write(metadata, new IIOImage(gridImage, null, metadata), writeParam);
       } finally {
          stream.close();
       }
       break;
    }
 }

 private void setDPI(IIOMetadata metadata) throws IIOInvalidTreeException {

    // for PMG, it's dots per millimeter
    double dotsPerMilli = 1.0 * DPI / 10 / INCH_2_CM;

    IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
    horiz.setAttribute("value", Double.toString(dotsPerMilli));

    IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
    vert.setAttribute("value", Double.toString(dotsPerMilli));

    IIOMetadataNode dim = new IIOMetadataNode("Dimension");
    dim.appendChild(horiz);
    dim.appendChild(vert);

    IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
    root.appendChild(dim);

    metadata.mergeTree("javax_imageio_1.0", root);
 }
Peter Kofler
  • 9,252
  • 8
  • 51
  • 79
  • why is it called grid image? anything different from a regular image? – Zeveso May 18 '12 at 21:25
  • @Zeveso I just happened to copy/paste this code from a working application that was saving an image of a grid, thus the name of the BufferedImage and the method. The code works for every BufferedImage. – Peter Kofler May 23 '12 at 11:42
  • 1
    @PeterKofler i am using above code without any changes. the above code creates PNG file as expected (using given dpi) but when i use above code for JPEG file it fails, do i need to set any parameter for JPEG or this code works only for PNG ? – Mihir Mar 18 '13 at 06:41
  • @Mihir I am not aware of any problem JPG would have. Can you be more specific how it fails? – Peter Kofler Mar 18 '13 at 06:55
  • 1
    @PeterKofler when i save jpeg with 400 dpi using above code by seeting formatName = "jpg" it saves the file , but when i open the saved file in irfanview , photoshop , inkspace all shos the file's dpi 72 DPI.if you need i can ask a separate question with related screen shots on SO. – Mihir Mar 18 '13 at 07:38
  • @Mihir , did you get anywhere with that? – planty182 Oct 20 '15 at 08:38
  • @Mihir Check out my answer for a variation of the above code that works with JPG: http://stackoverflow.com/a/39994920/261142 – Thomas Oct 12 '16 at 18:09
  • I used same but I am getting 72 dpi only – cody123 Oct 04 '19 at 03:11
4

Seting up TIFF DPI

If you want to set dpi for TIFF, try to do that by next steps:

private static IIOMetadata createMetadata(ImageWriter writer, ImageWriteParam writerParams, int resolution) throws
                                                                                                            IIOInvalidTreeException
{
    // Get default metadata from writer
    ImageTypeSpecifier type = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
    IIOMetadata meta = writer.getDefaultImageMetadata(type, writerParams);

    // Convert default metadata to TIFF metadata
    TIFFDirectory dir = TIFFDirectory.createFromMetadata(meta);

    // Get {X,Y} resolution tags
    BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
    TIFFTag tagXRes = base.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION);
    TIFFTag tagYRes = base.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION);

    // Create {X,Y} resolution fields
    TIFFField fieldXRes = new TIFFField(tagXRes, TIFFTag.TIFF_RATIONAL, 1, new long[][] { { resolution, 1 } });
    TIFFField fieldYRes = new TIFFField(tagYRes, TIFFTag.TIFF_RATIONAL, 1, new long[][] { { resolution, 1 } });

    // Add {X,Y} resolution fields to TIFFDirectory
    dir.addTIFFField(fieldXRes);
    dir.addTIFFField(fieldYRes);

    // Add unit field to TIFFDirectory (change to RESOLUTION_UNIT_CENTIMETER if necessary)
    dir.addTIFFField(new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), BaselineTIFFTagSet.RESOLUTION_UNIT_INCH));

    // Return TIFF metadata so it can be picked up by the IIOImage
    return dir.getAsMetadata();
}

Also, similar way you can setting up any TIFF tag.

Read more at the source

Community
  • 1
  • 1
Sergei Bubenshchikov
  • 5,275
  • 3
  • 33
  • 60
  • I think the code is not complete as it does not set the resolution unit, which is by default `RESOLUTION_UNIT_NONE`. In such case the reader/client should set ratio to 1/1, see [`TIFFImageWriter:1182`](https://github.com/jai-imageio/jai-imageio-core/blob/0feba94520cc9cb59c58167f3b13dd712f100bbc/src/main/java/com/github/jaiimageio/impl/plugins/tiff/TIFFImageWriter.java#L1182). Fix: `dir.addTIFFField(new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), BaselineTIFFTagSet.RESOLUTION_UNIT_INCH))`. Disclaimer: I am not TIFF expert. – dma_k Oct 12 '18 at 10:13
  • @dma_k feel free to edit answer if your suggestion works. I'm not a TIFF expert too ) – Sergei Bubenshchikov Oct 12 '18 at 11:14
  • As an additional reference I provide a link to [`TiffImage:146`](https://github.com/itext/itextpdf/blob/b5807a839ab5bc3725f1dc1924cb947e072e0a7c/itext/src/main/java/com/itextpdf/text/pdf/codec/TiffImage.java#L146), where one can see that X/Y DPI are set to zero if resolution unit is "none" and hence iTextPDF library interprets the image as having no DPI set. What clients are known to work correctly? – dma_k Oct 12 '18 at 20:55
3

i am using this code for tiff file in my project and it works well..

import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.media.jai.NullOpImage;
import javax.media.jai.OpImage;
import javax.media.jai.PlanarImage;
import com.sun.media.jai.codec.FileSeekableStream;
import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.ImageDecoder;
import com.sun.media.jai.codec.ImageEncoder;
import com.sun.media.jai.codec.SeekableStream;
import com.sun.media.jai.codec.TIFFEncodeParam;
import com.sun.media.jai.codec.TIFFField;
class SetDDPI
 {
static void tiff_Maker(List<BufferedImage> output, String result) throws   IOException
{
    TIFFEncodeParam params = new TIFFEncodeParam();
    OutputStream out = new FileOutputStream(result);
    List<BufferedImage> imageList = new ArrayList<BufferedImage>();
    for (int i = 1; i < output.size(); i++)
    {
        imageList.add(output.get(i));
    }
    params.setWriteTiled(true);
    params.setCompression(TIFFEncodeParam.COMPRESSION_GROUP4);
    params.setExtraImages(imageList.iterator());
    TIFFField[] extras = new TIFFField[2];
    extras[0] = new TIFFField(282, TIFFField.TIFF_RATIONAL, 1, (Object) new long[][] { { (long) 300, (long) 1 },
            { (long) 0, (long) 0 } });
    extras[1] = new TIFFField(283, TIFFField.TIFF_RATIONAL, 1, (Object) new long[][] { { (long) 300, (long) 1 },
            { (long) 0, (long) 0 } });
    params.setExtraFields(extras);
    ImageEncoder encoder = ImageCodec.createImageEncoder("tiff", out, params);
    encoder.encode(output.get(0));
    out.close();
}
static List<BufferedImage> tiff_Extractor(File tiff) throws IOException
{
    List<BufferedImage> images = new ArrayList<BufferedImage>();
    SeekableStream ss = new FileSeekableStream(tiff);
    ImageDecoder decoder = ImageCodec.createImageDecoder("tiff", ss, null);
    int numPages = decoder.getNumPages();
    for (int j = 0; j < numPages; j++)
    {
        PlanarImage op = new NullOpImage(decoder.decodeAsRenderedImage(j), null, null, OpImage.OP_IO_BOUND);
        images.add(op.getAsBufferedImage());

    }
    return images;
}
}

this is to set 300 DPI of Tiff image. you can change it according to your need.

extras[0] = new TIFFField(282, TIFFField.TIFF_RATIONAL, 1, (Object) new     
long[][] { { (long) 300, (long) 1 },{ (long) 0, (long) 0 } });

extras[1] = new TIFFField(283, TIFFField.TIFF_RATIONAL, 1, (Object) new     
long[][] { { (long) 300, (long) 1 },{ (long) 0, (long) 0 } });
GrizzlyManBear
  • 647
  • 8
  • 16
rj27
  • 89
  • 1
  • 9
  • Dude you don't know how much help this provided ... Thank you very much for sharing this !! – Dan Ortega Jul 17 '18 at 21:21
  • That works great to set DPI but how do I set the resolution? – user2179092 Dec 03 '18 at 14:38
  • @rj27 I tried the above code from tiff_maker to set dpi for a bufferedImage I have and then I try saving it to the local fs `writer.write(null, new IIOImage(image, null, null), null);`. After that I try to fetch the image and i get a gray screen with a size that is very large. Is there something I am missing? – xyzzz Sep 08 '21 at 19:18