How to Set Dpi Information in an Image

How to set DPI information in an image?

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);
}

How to change the DPI from 96 to 300 of an image in java after resizing?

BufferedImage image = ImageIO.read(new File(path));
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(new FileOutputStream(new File(path)));
JPEGEncodeParam jpegEncodeParam = jpegEncoder.getDefaultJPEGEncodeParam(image);
jpegEncodeParam.setDensityUnit(JPEGEncodeParam.DENSITY_UNIT_DOTS_INCH);
jpegEncoder.setJPEGEncodeParam(jpegEncodeParam); jpegEncodeParam.setQuality(0.75f, false);
jpegEncodeParam.setXDensity(300); jpegEncodeParam.setYDensity(300);
jpegEncoder.encode(image, jpegEncodeParam);
image.flush();

How to resize Image in Java with same or lower DPI

To store the DPI in an image implies that you want to save the image. (this wasn't clear in your question.) You need to specify the metadata directly in the encoder. Here's the JPEG version. I saw it's possible to PNG too it needs different metadata tree nodes.

[Edit] I found a way that doesn't rely on proprietary classes.

import org.w3c.dom.Element;

ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();

param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.95f);

IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), param);
Element tree = (Element)metadata.getAsTree("javax_imageio_jpeg_image_1.0");
Element jfif = (Element)tree.getElementsByTagName("app0JFIF").item(0);
jfif.setAttribute("Xdensity", Integer.toString(350));
jfif.setAttribute("Ydensity", Integer.toString(350));
jfif.setAttribute("resUnits", "1"); // In pixels-per-inch units
metadata.mergeTree("javax_imageio_jpeg_image_1.0", tree);

try (FileImageOutputStream output = new FileImageOutputStream(new File(filename))) {
writer.setOutput(output);
IIOImage iioImage = new IIOImage(image, null, metadata);
writer.write(metadata, iioImage, param);
writer.dispose();
}

Adapted from source

PNG version here

How to save image along with DPI information into clipboard?

The Device Independent Bitmap format contains some sort of DPI information, though from what I've seen it is generally not filled in on clipboard images. But if you want to use DIB to exchange data with something that actually reads that, then, sure, you can just fill it in.

I have detailed the ways to both set and extract DIB images through the Windows clipboard by manipulating the DIB header and data as bare bytes array in this answer:

A: Copying From and To Clipboard loses image transparency

The DPI values are not filled in in the code, but they are mentioned in comment in the clipboard DIB writing function ConvertToDib(Image image). Looking at the DIB header specs, the DPI values should be Int32 values put on offsets 0x18 and 0x1C. These values can probably be extracted from the input Image object given to the ConvertToDib function, but that'll be up to you to figure out exactly.

So if you just find the commented biXPelsPerMeter and biYPelsPerMeter mentions in that code and put the actual code there to fill in that data, that should work:

ArrayUtils.WriteIntToByteArray(fullImage, 0x18, 4, true, (UInt32)dpiX);
ArrayUtils.WriteIntToByteArray(fullImage, 0x1C, 4, true, (UInt32)dpiY);

DPI is dots per inch, though, while this seems to expect pure-integer pixels per meter, so if the Image object actually has it as DPI, some kind of conversion may be required there.

The same indices can be read when performing a clipboard paste (again, the code is in the answer I linked), though I haven't looked into how to actually put that information into the new Bitmap object. You'd probably have to expand the linked BuildImage function if you want to do that.

How to set DPI information in a grayscale image?

thanks one more time in advance.

I found an answer and one way to do this !!!

In the following link: How to change the DPI from 96 to 300 of an image in java after resizing? the "user3603284"posted an solution that helped me doing this.

I changed from png to jpeg (it does not matter for the project specifications) and then, worked like a charm !!!

The code:

File imageFile = new File("C:/ScannerOutput/scannerImage" + System.currentTimeMillis() +".jpeg");
FileOutputStream fos = new FileOutputStream(imageFile);
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(fos);
JPEGEncodeParam jpegEncodeParam = jpegEncoder.getDefaultJPEGEncodeParam(image);
jpegEncodeParam.setDensityUnit(JPEGEncodeParam.DENSITY_UNIT_DOTS_INCH);
jpegEncoder.setJPEGEncodeParam(jpegEncodeParam);
jpegEncodeParam.setQuality(0.75f, false);
jpegEncodeParam.setXDensity(resolutionState); //DPI rate 100, 200 or 300
jpegEncodeParam.setYDensity(resolutionState); //DPI rate 100, 200 or 300
jpegEncoder.encode(image, jpegEncodeParam);
image.flush();
fos.close();

Thanks very much SO, always helping me =)

With Python`s PIL, how to set DPI before loading an image?

AFAIK, while it can contain embedded bitmaps and a preview thumbnail, EPS is a vector-based format. It only makes sense to set DPI if you are generating output in a bitmap format.

You are right - I am trying to generate a bitmap picture from the eps. But opening (parsing?) an .eps-file with a certain resolution determines the actual pixel-size (given a certain document size). PythonMagick does this right but i would like to use PIL if possible. – OP

That is because the EPS driver in PythonMagick converts the EPS to a bitmap representation on input (remember IM, the underlying library, is a 'raster image processor') - while in PIL the EPS driver can also write EPS images.

See "A word about Vector Image formats" in ImageMagick:

Why is this important? Because IM is a 'raster image processor', and while it can read or write images stored in one of the vector formats it does so by converting the image to and from a internal raster image.
Consequently if you are trying to convert a image from a vector format, to another vector format, IM will essentially rasterize this image at the currently defined resolution or density which will hopefully (but unlikely) be suitable for the output device you intend to use it on.
In other words, any output from IM will never be a true vector format. While it can convert its internal raster format into a vector format file, the result is only a superficial vector image wrapper around an image in raster format. And unless the raster image is defined properly (at the right resolution) for the output device, the result will not be particularly good.
Unfortunately new uses to IM do not know anything about this. They see IM as a converter that can convert say PDF to Postscript, producing images with 'blocky' aliasing effects, 'washed out' colors, or blurry images that just do not look good at all, on the intended output device.
Which brings use to what I am trying to say...
Avoid using ImageMagick for 'Vector Image' to 'Vector Image' conversions
EG: converting between formats like: PDF, PS, SVG
In other words, use the right tool for the right job. And for this situation, ImageMagick is not the right tool.

See also the note about EPS on PIL:

PIL identifies EPS files containing image data, and can read files that contain embedded raster images (ImageData descriptors). If Ghostscript is available, other EPS files can be read as well. The EPS driver can also write EPS images.

[Update 1]

This information from Pillow docs is missing from the PIL docs:

If Ghostscript is available, you can call the load() method with the following parameter to affect how Ghostscript renders the EPS

scale


Affects the scale of the resultant rasterized image. If the EPS suggests that the image be rendered at 100px x 100px, setting this parameter to 2 will make the Ghostscript render a 200px x 200px image instead. The relative position of the bounding box is maintained:

im = Image.open(...)
im.size #(100,100)
im.load(scale=2)
im.size #(200,200)

[Update 2]

Contrary to my initial guess, PIL also rasterizes the image. When I saved as EPS it just made a wrapper around a bitmap. According to the OP the default resolution seems to be 72 ppi at his environment.

if you know the default resolution is 72 ppi (pixels per inch), calculating the scale for any density you want is a matter of simple proportion - given r as the resolution you want, s is the scale: 1 : s = 72 : r ergo:

im.load(scale=300.0/72.0)

May be it is best if you just specify the desired width instead of the resolution - for example if you want to have it 1677 pixels wide:

def open_eps(filename, width=None):
original_width = float(Image.open(filename).size[0])
im = Image.open(filename)
if width is not None:
im.load(scale=width/original_width)
return im

im = open_eps('testfile.eps', 1677)

So the final answer is: although there is no built-in parameter to specify the desired resolution in ppi while loading an EPS file, you can use the scale parameter to load it at any resolution you want. If you care enough, I guess the Pillow maintainers would be glad to receive a PR for this.

[Edit 3]

Paolo, the way is good, but it looks like scale is only accepting plain integers... 4,166666667 (300.0/72.0) is rounded to 4.

Shame on me for not testing.

def open_eps(filename, width=None):
original = [float(d) for d in Image.open(filename).size]
scale = width / original[0]
im = Image.open(filename)
if width is not None:
im.load(scale=math.ceil(scale))
if scale != 1:
im.thumbnail([int(scale * d) for d in original], Image.ANTIALIAS)
return im

im = open_eps('testfile.eps', 1677)

Not sure if I should use math.round instead of int but you got the idea.

Setting the DPI meta-information for a jpeg file in Android

To edit the value, you need to first create a byte[] array that will store the Bitmap.compress(). Here is a part of my code where I do just that(input being the source Bitmap).

ByteArrayOutputStream uploadImageByteArray = new ByteArrayOutputStream();
input.compress(Bitmap.CompressFormat.JPEG, 100, uploadImageByteArray);
byte[] uploadImageData = uploadImageByteArray.toByteArray();

Based on the JFIF structure, you need to edit the 13th, 14th, 15th, 16th, and 17th indexes in the byte array. 13th specifying the density type, 14th and 15th the X resolution, and 16th and 17th holding the Y resolution. I got the dpi using the following method:

private long getDPIinFloat(int width, int height) {
return (long) Math.sqrt(width * width + height * height) / 4;
}

After I got the DPI, I had to do some bit manipulation like so:

long firstPart = dpiInFloat >> 8;
if (GlobalState.debugModeOn) {
Log.d(TAG, "First Part: " + firstPart);
}
long lastPart = dpiInFloat & 0xff;
if (GlobalState.debugModeOn) {
Log.d(TAG, "Last Part: " + lastPart);
}

And then, manipulate the byte information like so:

uploadImageData[13] = 1;
uploadImageData[14] = (byte) firstPart;
uploadImageData[15] = (byte) lastPart;
uploadImageData[16] = (byte) firstPart;
uploadImageData[17] = (byte) lastPart;
//Upload Image data to the server

This way, I was able to set the dpi information on the metadata.

How to increase jpeg image ppi or dpi in java?

You can do it like in this answer, except you have to use "jpeg" as the format name and you need to implement the setDPI method to work with JPEG-specific metadata.

public static final String DENSITY_UNITS_NO_UNITS = "00";
public static final String DENSITY_UNITS_PIXELS_PER_INCH = "01";
public static final String DENSITY_UNITS_PIXELS_PER_CM = "02";

private BufferedImage gridImage;

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

final String formatName = "jpeg";

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 static void setDPI(IIOMetadata metadata) throws IIOInvalidTreeException {
String metadataFormat = "javax_imageio_jpeg_image_1.0";
IIOMetadataNode root = new IIOMetadataNode(metadataFormat);
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");

IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
app0JFIF.setAttribute("majorVersion", "1");
app0JFIF.setAttribute("minorVersion", "2");
app0JFIF.setAttribute("thumbWidth", "0");
app0JFIF.setAttribute("thumbHeight", "0");
app0JFIF.setAttribute("resUnits", DENSITY_UNITS_PIXELS_PER_INCH);
app0JFIF.setAttribute("Xdensity", String.valueOf(300));
app0JFIF.setAttribute("Ydensity", String.valueOf(300));

root.appendChild(jpegVariety);
root.appendChild(markerSequence);
jpegVariety.appendChild(app0JFIF);

metadata.mergeTree(metadataFormat, root);
}

This code sets the PPI to 300x300 but you may want to make this variable.



Related Topics



Leave a reply



Submit