Can anyone explain why this happens. I read an image and render it into an output writer. If it is a color file (or black and white), it renders fine. However, if the source image is grayscale, all I get is a black box.
Sample files available at https://www.dropbox.com/sh/kyfsh5curobwxrw/AACfWr1NhX8lPUZpzVGWIPQia?dl=0
My pom plugin dependancy snippets follow.
<dependency>
<groupId>javax.media</groupId>
<artifactId>jai_core</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>com.sun.media</groupId>
<artifactId>jai_imageio</artifactId>
<version>1.1</version>
</dependency>
A test program. I understand that this bit of code in itself is really of no value, but in reality it is part of a larger suite of operations. This code represents my efforts to narrow down the issue to a small piece of code.
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class GrayScaleImaging {
public static void main(String[] args)
throws IOException {
//works
// final File inputFile = new File("/home/vinayb/Downloads/page1_color.tif");
// final File outputFile = new File("/home/vinayb/Downloads/page1_color_mod.tif");
//doesn't work
final File inputFile = new File("/home/vinayb/Downloads/page1_grayscale.tif");
final File outputFile = new File("/home/vinayb/Downloads/page1_grayscale_mod.tif");
if (outputFile.exists()) {
outputFile.delete();
}
ImageReader imageReader = null;
ImageWriter imageWriter = null;
Graphics2D g = null;
try (final ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputFile);
final ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(outputFile);) {
//setup reader
imageReader = ImageIO.getImageReaders(imageInputStream).next();
imageReader.setInput(imageInputStream);
//read image
final BufferedImage initialImage = imageReader.read(0);
//prepare graphics for the output
final BufferedImage finalImage = new BufferedImage(initialImage.getWidth(), initialImage.getHeight(), imageType(initialImage));
g = finalImage.createGraphics();
//do something to the image
//doSomething(g)
//draw image
g.drawImage(initialImage, 0, 0, initialImage.getWidth(), initialImage.getHeight(), null);
//setup writer based on reader
imageWriter = ImageIO.getImageWriter(imageReader);
imageWriter.setOutput(imageOutputStream);
//write
imageWriter.write(null, new IIOImage(initialImage, null, imageReader.getImageMetadata(0)), imageWriter.getDefaultWriteParam());
} finally {
//cleanup
if (imageWriter != null) {
imageWriter.dispose();
}
if (imageReader != null) {
imageReader.dispose();
}
if (g != null) {
g.dispose();
}
}
}
private static int imageType(BufferedImage bufferedImage) {
return bufferedImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : bufferedImage.getType();
}
}
Okay, I have now fixed my decoder, thanks for the sample file! :-)
Anyway, after some research, I have come to the conclusion that the problem is definitively the sample file, not your code nor the library you are using.
The issue with this file is that the TIFF metadata contains PhotometricInterpretation == 3/Palette and ColorMap tags. Ie. the image uses indexed color model/palette. If the image is read as it should according to (my understanding of) the spec, using the supplied color map, the image comes out all black. If instead I ignore this, and rather read it as gray scale (assuming PhotometricInterpretation == 1/BlackIsZero), it comes out as black text on white (light gray) background.
Edit:
A better explanation, is that the values in the color map are all 8 bit quantities (using the low 8 bits of each color entry) instead of using the full 16 bit as they should... If I detect this while reading and creating a palette using only the low 8 bits, the image comes out as intended (as in DropBox). This is still a bad image according to the spec, but detectable.
Related
I want to compress JPEG to fixed file size (20480 bytes). Here is my code:
package io.github.baijifeilong.jpeg;
import lombok.SneakyThrows;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.FileImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
/**
* Created by BaiJiFeiLong#gmail.com at 2019/10/9 上午11:26
*/
public class JpegApp {
#SneakyThrows
public static void main(String[] args) {
BufferedImage inImage = ImageIO.read(new File("demo.jpg"));
BufferedImage outImage = new BufferedImage(143, 143, BufferedImage.TYPE_INT_RGB);
outImage.getGraphics().drawImage(inImage.getScaledInstance(143, 143, Image.SCALE_SMOOTH), 0, 0, null);
JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_DISABLED);
ImageWriter imageWriter = ImageIO.getImageWritersByFormatName("jpg").next();
imageWriter.setOutput(new FileImageOutputStream(new File("demo-xxx.jpg")));
imageWriter.write(null, new IIOImage(outImage, null, null), jpegParams);
}
}
And the error occured:
Exception in thread "main" javax.imageio.IIOException: JPEG compression cannot be disabled
at com.sun.imageio.plugins.jpeg.JPEGImageWriter.writeOnThread(JPEGImageWriter.java:580)
at com.sun.imageio.plugins.jpeg.JPEGImageWriter.write(JPEGImageWriter.java:363)
at io.github.baijifeilong.jpeg.JpegApp.main(JpegApp.java:30)
Process finished with exit code 1
So how to disable JPEG compression? Or there be any method that can compress any image to a fixed file size with any compression?
As for the initial question, how to create non-compressed jpegs: one can't, for fundamental reasons. While I initially assumed that it is possible to write a non-compressing jpg encoder producing output that can be decoded with any existing decoder by manipulating the Huffman tree involved, I had to dismiss it. The Huffman encoding is just the last step of quite a pipeline of transformations, that can not be skipped. Custom Huffman trees may also break less sophisticated decoders.
For an answer that takes into consideration the requirement change made in comments (resize and compress any way you like, just give me the desired file size) one could reason this way:
The jpeg file specification defines an End of Image marker. So chances are, that patching zeros (or just anything perhaps) afterwards make no difference. An experiment patching some images up to a specific size showed that gimp, chrome, firefox and your JpegApp swallowed such an inflated file without complaint.
It would be rather complicated to create a compression that for any image compresses precisely to your size requirement (kind of: for image A you need a compression ratio of 0.7143, for Image B 0.9356633, for C 12.445 ...). There are attempts to predict image compression ratios based on raw image data, though.
So I'd propose just to resize/compress to any size < 20480 and then patch it:
calculate the scaling ratio based on the original jpg size and the
desired size, including a safety margin to account for the
inherently vague nature of the issue
resize the image with that ratio
patch missing bytes to match exactly the desired size
As outlined here
private static void scaleAndcompress(String fileNameIn, String fileNameOut, Long desiredSize) {
try {
long size = getSize(fileNameIn);
// calculate desired ratio for conversion to stay within size limit, including a safte maring (of 10%)
// to account for the vague nature of the procedure. note, that this will also scale up if needed
double desiredRatio = (desiredSize.floatValue() / size) * (1 - SAFETY_MARGIN);
BufferedImage inImg = ImageIO.read(new File(fileNameIn));
int widthOut = round(inImg.getWidth() * desiredRatio);
int heigthOut = round(inImg.getHeight() * desiredRatio);
BufferedImage outImg = new BufferedImage(widthOut, heigthOut, BufferedImage.TYPE_INT_RGB);
outImg.getGraphics().drawImage(inImg.getScaledInstance(widthOut, heigthOut, Image.SCALE_SMOOTH), 0, 0, null);
JPEGImageWriter imageWriter = (JPEGImageWriter) ImageIO.getImageWritersByFormatName("jpg").next();
ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
imageWriter.setOutput(new MemoryCacheImageOutputStream(outBytes));
imageWriter.write(null, new IIOImage(outImg, null, null), new JPEGImageWriteParam(null));
if (outBytes.size() > desiredSize) {
throw new IllegalStateException(String.format("Excess output data size %d for image %s", outBytes.size(), fileNameIn));
}
System.out.println(String.format("patching %d bytes to %s", desiredSize - outBytes.size(), fileNameOut));
patch(desiredSize, outBytes);
try (FileOutputStream outFileStream = new FileOutputStream(new File(fileNameOut))) {
outBytes.writeTo(outFileStream);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void patch(Long desiredSize, ByteArrayOutputStream bytesOut) {
long patchSize = desiredSize - bytesOut.size();
for (long i = 0; i < patchSize; i++) {
bytesOut.write(0);
}
}
private static long getSize(String fileName) {
return (new File(fileName)).length();
}
private static int round(double f) {
return Math.toIntExact(Math.round(f));
}
A solution using Magick (sudo apt install imagemagick on Debian), maybe not work for some images. Thanks to #curiosa-g.
package io.github.baijifeilong.jpeg;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Scanner;
/**
* Created by BaiJiFeiLong#gmail.com at 2019/10/9 上午11:26
*/
public class JpegApp {
#SneakyThrows
private static InputStream compressJpeg(InputStream inputStream, int fileSize) {
File tmpFile = new File(String.format("tmp-%d.jpg", Thread.currentThread().getId()));
FileUtils.copyInputStreamToFile(inputStream, tmpFile);
Process process = Runtime.getRuntime().exec(String.format("mogrify -strip -resize 512 -define jpeg:extent=%d %s", fileSize, tmpFile.getName()));
try (Scanner scanner = new Scanner(process.getErrorStream()).useDelimiter("\\A")) {
if (process.waitFor() > 0) throw new RuntimeException(String.format("Mogrify Error \n### %s###", scanner.hasNext() ? scanner.next() : "Unknown"));
}
try (FileInputStream fileInputStream = new FileInputStream(tmpFile)) {
byte[] bytes = IOUtils.toByteArray(fileInputStream);
assert bytes.length <= fileSize;
byte[] newBytes = new byte[fileSize];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
Files.delete(Paths.get(tmpFile.getPath()));
return new ByteArrayInputStream(newBytes);
}
}
#SneakyThrows
public static void main(String[] args) {
InputStream inputStream = compressJpeg(new FileInputStream("big.jpg"), 40 * 1024);
IOUtils.copy(inputStream, new FileOutputStream("40KB.jpg"));
System.out.println(40 * 1024);
System.out.println(new File("40KB.jpg").length());
}
}
And the output:
40960
40960
I'm using something along the lines of this to do a (naive, apparently) check for the color space of JPEG images:
import java.io.*;
import java.awt.color.*;
import java.awt.image.*;
import javax.imageio.*;
class Test
{
public static void main(String[] args) throws java.lang.Exception
{
File f = new File(args[0]);
if (f.exists())
{
BufferedImage bi = ImageIO.read(f);
ColorSpace cs = bi.getColorModel().getColorSpace();
boolean isGrayscale = cs.getType() == ColorSpace.TYPE_GRAY;
System.out.println(isGrayscale);
}
}
}
Unfortunately this reports false for images that (visually) appear gray-only.
What check would do the right thing?
You can use this code:
File input = new File("inputImage.jpg");
BufferedImage image = ImageIO.read(input);
Raster ras = image.getRaster();
int elem = ras.getNumDataElements();
System.out.println("Number of Elems: " + elem);
If the number of elems returns 1, then its a greyscale image. If it returns 3, then its a color image.
the image looks like gray beacuse the r=g=b but actually it is a full color image, it has three channel r g b and the real gray image only have one channel
I'm writing a code to convert Microsoft power-point(ppt) slides into images and to write the generated images into pdf file. Following code generates and writes the images into pdf file but the problem i'm facing is, when i write image into pdf file it's size is exceeding the pdf page size and i can view only 75% of the image rest is invisible. One more thing to notice here is, written images in pdf file look like zoomed or expanded. Take a look at the following snippet of code:
for (int i = 0; i < slide.length; i++) {
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
graphics.setPaint(Color.white);
graphics.fill(new Rectangle(0, 0, pgsize.width, pgsize.height));
slide[i].draw(graphics);
fileName="C:/DATASTORE/slide-"+(i+1)+".png";
FileOutputStream out = new FileOutputStream(fileName);
javax.imageio.ImageIO.write(img, "png", out);
out.flush();
out.close();
com.lowagie.text.Image image =com.lowagie.text.Image.getInstance(fileName);
image.setWidthPercentage(40.0f);
doc.add((image));
}
doc.close();
} catch(DocumentException de) {
System.err.println(de.getMessage());
}
If anybody knows the solution please help me to rectify. Thank you.
Here is the code it accomplishes the task i wished. Now i'm getting the desired results after following Bruno Lowagie recommendations.
But, as Bruno Lowagie pointed out earlier, their is a problem in generated png image. The generated png image is not correct because shape or image in the slide overlaps with the texts of the slide. Can you help me to identify and rectify the error?
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.itextpdf.text.Image;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.usermodel.SlideShow;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfWriter;
public class ConvertSlidesIntoImages {
public static void main(String[] args){
try {
FileInputStream is = new FileInputStream("C:/DATASTORE/testPPT.ppt");
SlideShow ppt = new SlideShow(is);
is.close();
String fileName;
Dimension pgsize = ppt.getPageSize();
Slide[] slide = ppt.getSlides();
Document doc=new Document();
PdfWriter.getInstance(doc, new FileOutputStream("c:/DATASTORE/convertPPTSlidesIntoPDFImages.pdf"));
doc.open();
for (int i = 0; i < slide.length; i++) {
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
graphics.setPaint(Color.white);
graphics.fill(new Rectangle(0, 0, pgsize.width, pgsize.height));
slide[i].draw(graphics);
fileName="C:/DATASTORE/slide-"+(i+1)+".png";
FileOutputStream out = new FileOutputStream(fileName);
javax.imageio.ImageIO.write(img, "png", out);
out.flush();
out.close();
com.itextpdf.text.Image image =com.itextpdf.text.Image.getInstance(fileName);
doc.setPageSize(new com.itextpdf.text.Rectangle(image.getScaledWidth(), image.getScaledHeight()));
doc.newPage();
image.setAbsolutePosition(0, 0);
doc.add(image);
}
doc.close();
}catch(DocumentException de) {
System.err.println(de.getMessage());
}
catch(Exception ex) {
ex.printStackTrace();
}
}
Thank you
First this: If the png stored as "C:/DATASTORE/slide-"+(i+1)+".png" isn't correct, the slide in the PDF won't be correct either.
And this: Your code snippet doesn't show us how you create the Document object. By default, the page size is A4 in portrait. It goes without saying that images that are bigger than 595 x 842 don't fit that page.
Now the answer: There are two ways to solve this.
Either you change the size of the image (not with setWidthPercentage() unless you've calculated the actual percentage) and you add it a the position (0, 0) so that it doesn't take into account the margins. For instance:
image.scaleToFit(595, 842);
image.setAbsolutePosition(0, 0);
doc.add(image);
doc.newPage();
A better solution would be to adapt the size of the page to the size of the image.
Document doc = new Document(new Rectangle(image.getScaledWidth(), image.getScaledHeight()));
// create a writer, open the document
image.setAbsolutePosition(0, 0);
doc.add(image);
doc.newPage();
If the size of the images varies, you can change the page size while adding images like this:
doc.setPageSize(new Rectangle(image.getScaledWidth(), image.getScaledHeight()));
doc.newPage();
image.setAbsolutePosition(0, 0);
doc.add(image);
It is important to understand that the new page size will only come into effect after doc.newPage();
CAVEAT 1:
If your PDF only holds the last slide, you're probably putting all the slides on the same page, and the last slide covers them all. You need to invoke the newPage() method each time you add an image (as done in a code snippet in my answer).
CAVEAT 2:
Your allegation is wrong. According to the API docs, there is a method setPageSize(Rectangle rect), maybe you're using the wrong Rectangle class. If you didn't follow my advice (which IMHO wouldn't be wise), you're probably looking for com.lowagie.text.Rectangle instead of java.awt.Rectangle.
CAVEAT 3:
This is similar to CAVEAT 2, there are indeed no such methods in the class java.awt.Image, but as documented in the API docs, the class com.itextpdf.text.Image has a getScaleWidth() method and a getScaledHeight() method.
I don't know what to do with TIFF images, but I can't read or write any of them using straight Java standard ImageIO library. Any thoughts?
Thanks.
If you don't like or can't use JAI for any reason I have written a TIFF ImageReader plugin for ImageIO, available on GitHub. It is pure Java and does not need any native installs, and comes with a very friendly open source license (BSD).
It supports any baseline TIFF option, along with a lot of standard extensions. From version 3.1 the TIFF plugin also has write support.
With the proper JARs in your class path, usage can be as simple as:
BufferedImage image = ImageIO.read(inputTIFF);
// ...modify image (compose, resize, sharpen, etc)...
ImageIO.write(image, "TIFF", outputTIFF);
According to JEP 262: TIFF Image I/O the TIFF plugin that used to be part of JAI will be available as part of the Java SE, starting from Java 9.
That means, using Java 9 or later, the following code will just work, without any extra imports or dependencies:
BufferedImage image = ImageIO.read(inputTIFF);
// ...modify image (compose, resize, sharpen, etc)...
ImageIO.write(image, "TIFF", outputTIFF);
I haven't yet been able to verify the support for non-baseline TIFF flavors in this plugin, but I assume at least baseline TIFFs should be fully supported.
I tried JAI, and it didn't work for me.
Where are you stuck? Does the following work for you?
import java.io.File;
import java.io.FileOutputStream;
import java.awt.image.RenderedImage;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import javax.media.jai.NullOpImage;
import javax.media.jai.OpImage;
import com.sun.media.jai.codec.SeekableStream;
import com.sun.media.jai.codec.FileSeekableStream;
import com.sun.media.jai.codec.TIFFDecodeParam;
import com.sun.media.jai.codec.ImageDecoder;
import com.sun.media.jai.codec.ImageCodec;
public class Main {
public static void main(String args[]) {
File file = new File("input.tif");
try {
SeekableStream s = new FileSeekableStream(file);
TIFFDecodeParam param = null;
ImageDecoder dec = ImageCodec.createImageDecoder("tiff", s, param);
RenderedImage op = new NullOpImage(dec.decodeAsRenderedImage(0),
null,
OpImage.OP_IO_BOUND,
null);
FileOutputStream fos = new FileOutputStream("output.jpg");
JPEGImageEncoder jpeg = JPEGCodec.createJPEGEncoder(fos);
jpeg.encode(op.getData());
fos.close();
}
catch (java.io.IOException ioe) {
System.out.println(ioe);
}
}
}
Java supports TIFF format only from Java 9 release. If you are trying to use ImageIO for TIFF into older Java version it will give you exception.
If you want to use TIFF in earlier version as well, you Twelve Monkey plugin along with Java just by adding dependency of Twelve Monkey.
Maven Dependency for Twelve Monkey:
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.5</version>
</dependency>*
I'm also giving example to merge the multiple images into Single TIFF with pages using Twelve Monkey,
BufferedImage b1 = null;
BufferedImage b2 = null;
TIFFImageReaderSpi SPI = new TIFFImageReaderSpi();
ImageReader imageReader1 = SPI.createReaderInstance();
ImageInputStream iis1 = ImageIO.createImageInputStream(new File("1.tif"));
imageReader1.setInput(iis1);
b1 = imageReader1.read(0);
ImageReader imageReader2 = SPI.createReaderInstance();
ImageInputStream iis2 = ImageIO.createImageInputStream(new File("2.tif"));
imageReader2.setInput(iis2);
b2 = imageReader2.read(0);
ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();
writer.setOutput(ImageIO.createImageOutputStream(new File("3.tif")));
ImageWriteParam writeParam = writer.getDefaultWriteParam();
//writeParam.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
//writeParam.setCompressionType("Deflate");
writer.prepareWriteSequence(null);
IIOImage i1 = new IIOImage(b1, null, null);
IIOImage i2 = new IIOImage(b2, null, null);
writer.writeToSequence(i1, writeParam);
writer.writeToSequence(i2, writeParam);
writer.endWriteSequence();
writer.dispose();
The above code will work with Java8 and written to open two TIFF image and merge into single.
Also, you may use compression as needed. Just use comment lines to add compression.
Add Maven dependency :
<dependency>
<groupId>org.geotoolkit</groupId>
<artifactId>geotk-coverageio</artifactId>
<version>3.17</version>
</dependency>
Code example :
import org.geotoolkit.image.io.plugin.RawTiffImageReader;
IIORegistry registry = IIORegistry.getDefaultInstance();
registry.registerServiceProvider(new RawTiffImageReader.Spi());
String[] a = ImageIO.getReaderFileSuffixes();
for (int i=0; i<a.length; i++) {
System.out.println(a[i]);
}
BufferedImage image = ImageIO.read(new File("C:\\mypic.tiff"));
ImageIO.write(image, "jpg",new File("C:\\out.jpg"));
ImageIO.write(image, "gif",new File("C:\\out.gif"));
ImageIO.write(image, "png",new File("C:\\out.png"));
ImageIO.write(image, "tif",new File("C:\\out.tiff"));
I am trying to programatically set the dpi metadata of an jpeg image in Java. The source of the image is a scanner, so I get the horizontal/vertical resolution from TWAIN, along with the image raw data. I'd like to save this info for better print results.
Here's the code I have so far. It saves the raw image (byteArray) to a JPEG file, but it ignores the X/Ydensity information I specify via IIOMetadata. Any advice what I'm doing wrong?
Any other solution (third-party library, etc) would be welcome too.
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream
import org.w3c.dom.Element;
import com.sun.imageio.plugins.jpeg.JPEGImageWriter;
public boolean saveJpeg(int[] byteArray, int width, int height, int dpi, String file)
{
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
WritableRaster wr = bufferedImage.getRaster();
wr.setPixels(0, 0, width, height, byteArray);
try
{
// Image writer
JPEGImageWriter imageWriter = (JPEGImageWriter) ImageIO.getImageWritersBySuffix("jpeg").next();
ImageOutputStream ios = ImageIO.createImageOutputStream(new File(file));
imageWriter.setOutput(ios);
// Compression
JPEGImageWriteParam jpegParams = (JPEGImageWriteParam) imageWriter.getDefaultWriteParam();
jpegParams.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(0.85f);
// Metadata (dpi)
IIOMetadata data = imageWriter.getDefaultImageMetadata(new ImageTypeSpecifier(bufferedImage), jpegParams);
Element tree = (Element)data.getAsTree("javax_imageio_jpeg_image_1.0");
Element jfif = (Element)tree.getElementsByTagName("app0JFIF").item(0);
jfif.setAttribute("Xdensity", Integer.toString(dpi));
jfif.setAttribute("Ydensity", Integer.toString(dpi));
jfif.setAttribute("resUnits", "1"); // density is dots per inch
// Write and clean up
imageWriter.write(data, new IIOImage(bufferedImage, null, null), jpegParams);
ios.close();
imageWriter.dispose();
}
catch (Exception e)
{
return false;
}
return true;
}
Thanks!
Some issues that were not considered here:
The tree is not directly mapped to the IOMetaData. To apply data from tree, add following call after setting the densities and raster parameters:
data.setFromTree("javax_imageio_jpeg_image_1.0", tree);
don't use the meta data as first parameter in the write call. See JPEGImageWriter#write(IIOMetaData, IIOImage, ImageWriteParam). If streamMetaData is not NULL, a warning (WARNING_STREAM_METADATA_IGNORED) will be generated.
set the meta data as IOMetadata of the IOImage. These meta data are used by JPEGImageWriter. The correct write call then is
imageWriter.write(null, new IIOImage(F_scaledImg, null, data), jpegParams);
I would seem this could be a bug.
I found this post from a few google searches
Apparently there are alot more that point to a bug as well.
The post above talks about using JMagick as a third party work around.