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
Get the Changed HTML Content After It's Updated by JavaScript? (Htmlunit)
Bug with Varargs and Overloading
Difference of Maven Jaxb Plugins
How to Get Names of Classes Inside a Jar File
Creating Random Colour in Java
Httpurlconnection Invalid Http Method: Patch
Mockito:How to Verify Method Was Called on an Object Created Within a Method
Can You Explain the Httpurlconnection Connection Process
How to Intentionally Cause a Custom Java Compiler Warning Message
Using Powermockito.Whennew() Is Not Getting Mocked and Original Method Is Called
Jschexception: Algorithm Negotiation Fail
Cannot Resolve Constructor Firefoxdriver(Org.Openqa.Selenium.Firefox.Firefoxprofile)
Difference Between & and && in Java
How to Replace Special Characters in a String
Streaming Large Result Sets with MySQL
Converting Characters to Integers in Java