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.
Related
I have a java program which capture screen shots at a regular interval using java Robot class.But since it takes screen shots very often(~5 sec) it could fill my hard disk very soon.
Is there any way through which i could decrease the size of image before saving, but could regenerate the original image without losing the quality.
import java.io.*;
import java.util.*;
import java.awt.*;
import java.io.*;
import java.awt.image.BufferedImage;
import java.awt.*;
import com.mysql.jdbc.exceptions.jdbc4.CommunicationsException;
import java.sql.*;
import java.util.*;
import javax.imageio.ImageIO;
class wtd
{
public static BufferedImage getImage()throws Exception
{
Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
Robot robot = new Robot();
BufferedImage image = robot.createScreenCapture(new Rectangle(0, 0, (int) screenDim.getWidth(),(int) screenDim.getHeight()));
return image;
}
public static void main(String args[])throws Exception
{
long id=0;
try{
while(true)
{
BufferedImage originalImage=getImage();
ImageIO.write(originalImage, "jpg", new File("D:/"+id+".jpg"));
id++;
}
}
catch(Exception e){}
}
}
If your images are already compressed, using another compression processing will not be very helpful : you will spare space but not necessarily a lot of.
If your images are not or few compressed, you could use classic compression tools as zip that allows lossless data compression.
You have multiple level of compression but more the level of compression is high, more the processing time is long.
So according to the cpu power, the available cpu thread number and the size of your images, you should use a level more or less important of compression.
For example the java.util.zip.ZipOutputStream class allows to create a zip with a specific level of compression by invoking the setLevel(int level) method.
Then you could use the java.util.zip.ZipInputStream class to extract the archive.
Edit for a code example :
Here is a not tested example by using the javax.imageio.ImageIO.write() JDK 8 specific method that allows to write a java.awt.image.BufferedImage into an java.io.OutputStream object:
// Here is the capture bufferedImage from your application
BufferedImage screenShot = ...;
// You create the zip file and you add entry that will store the image
FileOutputStream fileOut = new FileOutputStream("yourZipFile.zip");
ZipOutputStream zipOut = new ZipOutputStream(fileOut);
zipOut.setLevel(9); // 9 is the max level
ZipEntry zipEntry = new ZipEntry("screenshot-2017-03-24_12-03-30.jpg");
zipOut.putNextEntry(zipEntry);
// you get the bytes from the image
ByteArrayOutputStream out = new ByteArrayOutputStream();
javax.imageio.ImageIO.write(screenShot, "jpg", out);
byte[] bytes = out.toByteArray();
// you write the bytes in the zipOutputStream
zipOut.write(bytes, 0, bytes.length);
zipOut.close();
If you are looking for lossless compression you can give a try to Lepton which achieves:
a 22% savings reduction for existing JPEG images, by predicting coefficients in JPEG blocks and feeding those predictions as context into an arithmetic coder. Lepton preserves the original file bit-for-bit perfectly. It compresses JPEG files at a rate of 5 megabytes per second and decodes them back to the original bits at 15 megabytes per second, securely, deterministically, and in under 24 megabytes of memory.
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.
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"));
On the JFreeChart web site it says that the library can output the chart in vector format.
From the JFreeChart site:
support for many output types, including Swing components, image files (including PNG and JPEG), and vector graphics file formats
(including PDF, EPS and SVG);
But how can I actually output in SVG format?
There is a way using the Apache Batik library, but from the statement above I would think JFreeChart can do it without Batik.
I could figure out the output for PNG and JPG in the ChartUtilities class, but there seems to be no class for vector graphics output.
No, JFreeChart supports SVG in the sense that it can be used in conjunction with Batik or JFreeSVG, which are required. Related resources include these:
The JFreeChart Developer Guide†, discussed here.
Saving JFreeChart as SVG vector images using Batik.
JFreeSVG, which can "generate content in SVG format using the standard Java2D drawing API, Graphics2D." Demonstration programs may be found here, including this SVGBarChartDemo1 excerpt:
JFreeChart chart = createChart(createDataset());
SVGGraphics2D g2 = new SVGGraphics2D(600, 400);
Rectangle r = new Rectangle(0, 0, 600, 400);
chart.draw(g2, r);
File f = new File("SVGBarChartDemo1.svg");
SVGUtils.writeToSVG(f, g2.getSVGElement());
†Disclaimer: Not affiliated with Object Refinery Limited; just a satisfied customer and very minor contributor.
To just make it simple for other readers, the following code converts a jFreeChart to a SVG by using jFreeSVG:
import org.jfree.graphics2d.svg.SVGGraphics2D;
import org.jfree.chart.JFreeChart;
import java.awt.geom.Rectangle2D;
public String getSvgXML(){
final int widthOfSVG = 200;
final int heightOfSVG = 200;
final SVGGraphics2D svg2d = new SVGGraphics2D(widthOfSVG, heightOfSVG);
final JFreeChart chart = createYourChart();
chart.draw(svg2d,new Rectangle2D.Double(0, 0, widthOfSVG, heightOfSVG));
final String svgElement = svg2d.getSVGElement();
return svgElement;
}
To write the SVG elements to a PDF file, you can use the following code to generate a byte[] out of your SVG and then write it to a file. For this case I use apache batic :
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import org.apache.batik.transcoder.Transcoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.fop.svg.PDFTranscoder;
public byte[] getSVGInPDF(){
final Transcoder transcoder = new PDFTranscoder();
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final TranscoderInput transcoderInput = new TranscoderInput(
new ByteArrayInputStream(getSvgXML().getBytes()));
final TranscoderOutput transcoderOutput = new TranscoderOutput(outputStream);
transcoder.transcode(transcoderInput, transcoderOutput);
return outputStream.toByteArray();
}
In addition to trashgod 's answer
It seems that JFreeSVG is far more efficient than Batik : http://www.object-refinery.com/blog/blog-20140423.html