How to improve quality of signature image embedded in pdf - java

I am working on an web application to embed signature to pdf document. I am using following library Zetakey Sign & Send.From the signature pad signature is captured using:
var dataURL = canvas.toDataURL("image/png",1);
And in the server side(first Base64 decoding of signature string):
public String createSignature(String mySignature,int width,int height) throws IOException {
String filePath = SIGNATURE_PATH +"signature_" + new Date().getTime() + ".png";
byte[] imageByteArray = decodeImage(mySignature);
try(FileOutputStream imageOutFile = new FileOutputStream(filePath)){
imageOutFile.write(imageByteArray);
}
try {
BufferedImage image = ImageIO.read(new File(filePath));
image = scaleImage(image, width, height);
ImageIO.write(image, "png", new File(filePath));
for(Iterator<ImageWriter> iw = ImageIO.getImageWritersByFormatName("png"); 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,500,width,height);
try (ImageOutputStream stream = ImageIO.createImageOutputStream(new FileOutputStream(filePath))){
writer.setOutput(stream);
writer.write(metadata, new IIOImage(image, null, metadata), writeParam);
}
break;
}
} catch (IOException e) {
logger.error(e.getMessage());
}
return filePath;
}
private static void setDPI(IIOMetadata metadata, int value, int width, int height) throws IIOInvalidTreeException {
double dotsPerMilli = value/(INCH_TO_CM*10);
IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
horiz.setAttribute(VAL, Double.toString(dotsPerMilli));
IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
vert.setAttribute(VAL, Double.toString(dotsPerMilli));
IIOMetadataNode horizScreenSize = new IIOMetadataNode("HorizontalScreenSize");
horizScreenSize.setAttribute(VAL, Integer.toString(width));
IIOMetadataNode vertScreenSize = new IIOMetadataNode("VerticalScreenSize");
vertScreenSize.setAttribute(VAL, Integer.toString(height));
IIOMetadataNode dim = new IIOMetadataNode("Dimension");
dim.appendChild(horiz);
dim.appendChild(vert);
dim.appendChild(horizScreenSize);
dim.appendChild(vertScreenSize);
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
root.appendChild(dim);
metadata.mergeTree("javax_imageio_1.0", root);
}
public static BufferedImage scaleImage(BufferedImage original, int width, int height){
BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(((float)width)/original.getWidth(), ((float)height)/original.getHeight());
Map<RenderingHints.Key, Object> map = new HashMap<>();
map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints hints = new RenderingHints(map);
AffineTransformOp scaleOp = new AffineTransformOp(at, hints);
scaled = scaleOp.filter(original, scaled);
return scaled;
}
But the quality of signature image printed on pdf is not at all satisfactory even after attempting 500 DPI.Am I missing something?

So, using the ZetaKey's live-demo, the signature capture returned an acceptable quality PNG image...
Consider the following things:
Can I just use the original, unaltered image?
If not, can I change the order of my transformations? (DPI -> Scale Vs Scale -> DPI )
Can I forgo scaling? (often the cause of resolution loss)
I suspect that you are losing image resolution because you're adjusting DPI after you scale the image.
Effects of Scaling and DPI:
Effects of Image Transformations (DPI)
Excerpt: (not directly related, but may give insight into problem)
Now suppose you want to print your image using a regular offset press, which can print up to 300dpi images. Suppose you try to print it exactly at the original image dimension in inches. The press needs 300 dots for every inch to do a good job. The image you are supplying has only 72 dots for every inch, so the press (or more accurately, the software that prepares the plates for the press) has to make up the missing dots. The result will be blurry or noisy because there will be a lot of "transitional" dots that the software will creatively add to fill the missing gaps.

Related

How to resize Image in Java with same or lower DPI

I am trying to resize jpg Image files in Java. For this I am using Scalr.
I have around 16MB image with 6000x4000 Resolution and 350 dpi.
When I resize it to 4500 width, it downscales the DPI also to 96.
This is the code I am using:
Scalr.resize(img, Scalr.Method.ULTRA_QUALITY, 4500, Scalr.OP_ANTIALIAS);
I tried it without any library with the code as:
private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type, int IMG_WIDTH,
int IMG_HEIGHT) {
BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
g.dispose();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
return resizedImage;
}
But the result was same. So how can I resize the images with dpi around 150 if possible and same 350 dpi if not possible.
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
this work for me, jpg 72 -> 300
public static void handleDpi(File file, int xDensity, int yDensity) {
try {
BufferedImage image = ImageIO.read(file);
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(new FileOutputStream(file));
JPEGEncodeParam jpegEncodeParam = jpegEncoder.getDefaultJPEGEncodeParam(image);
jpegEncodeParam.setDensityUnit(JPEGEncodeParam.DENSITY_UNIT_DOTS_INCH);
jpegEncoder.setJPEGEncodeParam(jpegEncodeParam);
jpegEncodeParam.setQuality(0.75f, false);
jpegEncodeParam.setXDensity(xDensity);
jpegEncodeParam.setYDensity(yDensity);
jpegEncoder.encode(image, jpegEncodeParam);
image.flush();
} catch (IOException e) {
e.printStackTrace();
}
}

JasperReports with Barcode Code128 (Barcode4J): PNG differs from PDF

I'm printing a label with following number "1000049722ABCD2F" as a barcode, using a Jasper-template and the barcode-element "Code128" from Barcode4J. Printed as a PDF, the barcode is perfect and scannable.
Printing same barcode as a PNG-file, the barcode is not scannable and it obviously differs from the PDF-barcode:
Same barcode: PDF (upper part of an image) and PNG (lower part of an image):
Following code is used to generate the PDF-barcode:
byte[] data = JasperExportManager.exportReportToPdf(jasperPrint);
To generate PNG, it's not that simple:
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(byteOutputStream);
ImageWriter imageWriter = ImageIO.getImageWritersBySuffix("png").next();
imageWriter.setOutput(imageOutputStream);
float zoom = getZoomFactor(jasperPrint);
BufferedImage image = new BufferedImage(
(int)(jasperPrint.getPageWidth() * zoom ) + 1,
(int)(jasperPrint.getPageHeight() * zoom) + 1,
BufferedImage.TYPE_INT_RGB);
JRGraphics2DExporterNoAntialias exporter = new JRGraphics2DExporterNoAntialias();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRGraphics2DExporterParameter.GRAPHICS_2D, image.getGraphics());
exporter.setParameter(JRExporterParameter.PAGE_INDEX, Integer.valueOf(page));
exporter.setParameter(JRGraphics2DExporterParameter.ZOOM_RATIO, new Float(zoom));
exporter.exportReport();
IIOMetadata imageMetaData = imageWriter.getDefaultImageMetadata(new ImageTypeSpecifier(image), null);
// DPI = 200, inch = 25.4
double dotsPerMilli = 200 / 25.4;
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);
imageMetaData.mergeTree("javax_imageio_1.0", root);
imageWriter.write(null, new IIOImage(image, null, imageMetaData), null);
imageOutputStream.close();
imageWriter.dispose();
byte[] data = byteOutputStream.toByteArray();
I am using:
barcode4j-2.1 / jasperreports-5.0.0 / 200 DPI is a required size for my label printer
I tried to change quite some settings (BufferedImage.TYPE-value, width of barcode, Barbecue-barcode 128B and others), but there is always a difference between the PDF and PNG-barcode.
GOAL: PNG-barcode should be exactly the same as the PDF-barcode.
Could anyone help me with this? I would greatly appreciate it!
Instead of using ImageWriter, why don't you try using MimeTypes.MIME_PNG from org.krysalis.barcode4j.tools.MimeTypes (from barcode4j-2.1.jar itself).
I'll not repeat my answer but you can refer to my codes. Here's the link to my other stackoverflow:
Barcode4j as png image
and I've never face this issue (unable to scan barcode) using the program.

Write PNG file with less disk size in Java

I have a BufferedImage:
BufferedImage bi = new BufferedImage(14400, 14400, BufferedImage.TYPE_INT_ARGB);
I have saved this image to a PNG file using the following code:
public static void saveGridImage(BufferedImage sourceImage, int DPI,
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, DPI);
final ImageOutputStream stream = ImageIO
.createImageOutputStream(output);
try {
writer.setOutput(stream);
writer.write(metadata,
new IIOImage(sourceImage, null, metadata), writeParam);
} finally {
stream.close();
}
break;
}
}
public static void setDPI(IIOMetadata metadata, int DPI)
throws IIOInvalidTreeException {
double INCH_2_CM = 2.54;
// for PNG, 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);
}
When the code executes it creates an PNG file with 400 DPI and Disk Size of 168 MB; this is too much.
Is there any way or parameters I can use to save a smaller PNG?
Before, I had a 1.20 GB TIFF file, and when I converted it to PNG using imagemagick at 400 DPI, the resulting file size was only 700 KB.
So, I think I might be able to save the above file smaller.
Can pngj help me? Because I now have a png file which I can read in pngj library.
A 14400x14400 ARGB8 image has a raw (uncompressed) size of 791MB. It will compress more or less according to its nature (has uniform or smooth zones) and according (less important) to the PNG compression parameters.
when i convert it using imagemagic to PNG using 400 DPI , the
resulting file size is only 700 KB.
(I don't understand why you speak of DPI, that has nothing to do, what matters is the size in pixels) Are you saying that you are getting a 14400x14400 ARGB of 700KB? That would represent a compression of 1/1000, hard to believe unless the image is practically flat.
You should first understand what is going on here.
Anyway, here's a sample code with PNGJ
/** writes a BufferedImage of type TYPE_INT_ARGB to PNG using PNGJ */
public static void writeARGB(BufferedImage bi, OutputStream os) {
if(bi.getType() != BufferedImage.TYPE_INT_ARGB)
throw new PngjException("This method expects BufferedImage.TYPE_INT_ARGB" );
ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
PngWriter pngw = new PngWriter(os, imi);
pngw.setCompLevel(9);// maximum compression, not critical usually
pngw.setFilterType(FilterType.FILTER_AGGRESSIVE); // see what you prefer here
DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
SinglePixelPackedSampleModel samplemodel = (SinglePixelPackedSampleModel) bi.getSampleModel();
if(db.getNumBanks()!=1)
throw new PngjException("This method expects one bank");
ImageLine line = new ImageLine(imi);
for (int row = 0; row < imi.rows; row++) {
int elem=samplemodel.getOffset(0,row);
for (int col = 0,j=0; col < imi.cols; col++) {
int sample = db.getElem(elem++);
line.scanline[j++] = (sample & 0xFF0000)>>16; // R
line.scanline[j++] = (sample & 0xFF00)>>8; // G
line.scanline[j++] = (sample & 0xFF); // B
line.scanline[j++] = (((sample & 0xFF000000)>>24)&0xFF); // A
}
pngw.writeRow(line, row);
}
pngw.end();
}
I would attempt to fiddle with the settings on the writeParam object you're creating. Currently you're calling getDefaultWriteParam(); which gives you a basic writeParam object. My guess is the default would be NO compression.
After doing that, you can probably set some of the compression modes to reduce the file size.
writeParam.setCompressionMode(int mode);
writeParam.setCompressionQuality(float quality);
writeParam.setCompressionType(String compressionType);
See http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageWriteParam.html
And specifically http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageWriteParam.html#setCompressionMode(int)
sample code for pngj that works for 2.x versions of the leonbloy's pngj library
/** writes a BufferedImage of type TYPE_INT_ARGB to PNG using PNGJ */
public static void writePNGJARGB(BufferedImage bi, /*OutputStream os, */File file) {
System.out.println(".....entering PNGj alternative image file save mode....." );
if(bi.getType() != BufferedImage.TYPE_INT_ARGB) throw new PngjException("This method expects BufferedImage.TYPE_INT_ARGB" );
ImageInfo imi = new ImageInfo(bi.getWidth(), bi.getHeight(), 8, true);
PngWriter pngw = new PngWriter(file, imi, false);
// PngWriter pngw = new PngWriter(file,imginfo,overwrite); //params
pngw.setCompLevel(7); // tuning compression, not critical usually
pngw.setFilterType(FilterType.FILTER_PAETH); // tuning, see what you prefer here
System.out.println("..... PNGj metadata = "+pngw.getMetadata() );
DataBufferInt db =((DataBufferInt) bi.getRaster().getDataBuffer());
if(db.getNumBanks()!=1) {
throw new PngjException("This method expects one bank");
}
SinglePixelPackedSampleModel samplemodel = (SinglePixelPackedSampleModel) bi.getSampleModel();
ImageLineInt line = new ImageLineInt(imi);
int[] dbbuf = db.getData();
for (int row = 0; row < imi.rows; row++) {
int elem=samplemodel.getOffset(0,row);
for (int col = 0,j=0; col < imi.cols; col++) {
int sample = dbbuf[elem++];
line.getScanline()[j++] = (sample & 0xFF0000)>>16; // R
line.getScanline()[j++] = (sample & 0xFF00)>>8; // G
line.getScanline()[j++] = (sample & 0xFF); // B
line.getScanline()[j++] = (((sample & 0xFF000000)>>24)&0xFF); // A
}
//pngw.writeRow(line, /*imi.rows*/);
pngw.writeRow(line);
}
pngw.end();
}

Insufficient Data For An Image (PDF File Generation)

I'm using PDFBox to generate PDF files, however when I try to draw an image which I receive from an array of bytes I get the following error:
Insufficient data for an image
This is the basic structure of my code:
public ByteArrayOutputStream generatePDF() {
.. Variable Declaration
// Creating Document
document = new PDDocument();
// Creating Pages
for(int i = 0; i < arrayVar.length; i++) {
// Adding page to document
page = new PDPage();
// Creating FONT Attributes
fontNormal = PDType1Font.HELVETICA;
fontBold = PDType1Font.HELVETICA_BOLD;
// Building Front & Back Invoice Images
singleImageMap = // Getting Map With Array Of Bytes from Web Service Call;
if(singleImageMap != null && !singleImageMap.isEmpty()) {
arrayFront = Utils.readImage((byte[]) singleImageMap.get(Constants.WS_IMAGE_FRONT));
arrayBack = Utils.readImage((byte[]) singleImageMap.get(Constants.WS_IMAGE_BACK));
fileFront = new ByteArrayInputStream(arrayFront);
fileBack = new ByteArrayInputStream(arrayBack);
bufferedImageFront = ImageIO.read(fileFront);
bufferedImageBack = ImageIO.read(fileBack);
rescaledFrontImg = Scalr.resize(bufferedImageFront, 500);
rescaledBackImg = Scalr.resize(bufferedImageBack, 500);
front = new PDJpeg(document, rescaledFrontImg);
back = new PDJpeg(document, rescaledBackImg);
}
// Next we start a new content stream which will "hold" the to be created content.
contentStream = new PDPageContentStream(document, page);
// Let's define the content stream
contentStream.beginText();
contentStream.setFont(fontNormal, 8);
contentStream.moveTextPositionByAmount(200, 740);
contentStream.drawString("NAME: " + arrayVar[i].getParameter(Constants.NAME));
contentStream.endText();
if(front != null && back != null) {
contentStream.drawImage(front, 55, 500);
contentStream.drawImage(back, 55, 260);
}
// Add Page
document.addPage(page);
// Let's close the content stream
contentStream.close();
}
// Let's create OutputStream object
output = new ByteArrayOutputStream();
// Finally Let's save the PDF
document.save(output);
document.close();
return output;
}
Since I receive a PNG file from the Web Service I do the conversion to JPG with the following method:
public static byte[] readImage(byte[] file) throws Exception {
ImageInputStream is = ImageIO.createImageInputStream(new ByteArrayInputStream(file));
BufferedImage originalImage = ImageIO.read(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "jpg", baos );
byte[] imageInByte = baos.toByteArray();
return imageInByte;
}
As per this link:
https://issues.apache.org/jira/browse/PDFBOX-849
It points out that the error is because the PDJepg object should be created before the creation of the contentStream, but that's what I do in my code.
I'm not sure if there is a problem with the structure of my code, or that maybe there is an error in the way I'm handling the image bytes I'm getting from the Web Service call.
Does anyone has an idea of what could be the problem?
UPDATE
I did what Zelter Ady and indeed the image that I'm getting from the Web Service is valid since I was able to generate a physical file with it, so the problem should be somewhere around the manipulation of the image, the thing is I don't know what I'm missing.
I've got the same problem. With some images, Acrobat failed to display pages with this message:
Insufficient data for an image
My problem came from the colorModel in some jpeg images.
To track which images weren't ok, i log the BufferedImage colorModel by log.warn(img.getColorModel());
[VisualLocatorServlet.doGet:142] ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace#4b7fce transparency = 1 has alpha = false isAlphaPre = false
[VisualLocatorServlet.doGet:142] ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace#4b7fce transparency = 1 has alpha = false isAlphaPre = false
[VisualLocatorServlet.doGet:142] ColorModel: #pixelBits = 8 numComponents = 1 color space = java.awt.color.ICC_ColorSpace#19ef899 transparency = 1 has alpha = false isAlphaPre = false
Obviously, failing images are 8-bits encoded.
To fix that, i did the following:
byte[] buffer = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
BufferedImage img = ImageIO.read(new URL(visual));
/* resample 8-bits to 24-bits if necessary to fix pdf corruption */
if(img.getColorModel().getNumColorComponents()==1){
log.warn("components #1"+img.getColorModel());
BufferedImage out = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g2 = out.createGraphics();
g2.setBackground(Color.WHITE);
g2.drawImage(i, 0, 0, null);
g2.dispose();
log.warn("redrawn image "+img.getColorModel());
}
ImageIO.write(img, "jpeg", out);
...
The main point is to recreate a BufferedImage in 24bits. (BufferedImage.TYPE_3BYTE_BGR).
This may be an issue on the Adobe viewer side rather than at creation time. There's a known issue with the latest Acrobat versions: “Insufficient data for an image” error after updating to 10.1.4 or 9.5.2:
http://blogs.adobe.com/dmcmahon/2012/08/21/acrobat-insufficient-data-for-an-image-error-after-updating-to-10-1-4-or-9-5-2/
Before the build of the pdf try to save the image in a file, just to see the image is complete and can be saved.
You may use something like this to test the received image:
System.IO.File.WriteAllBytes("c:\\tmp.png", (byte[]) singleImageMap.get(Constants.FRONT));
and then open the image in a imageviewer. If the image cannot be open, then u have an error here. If the image is ok.... at least you know that this part is ok!
Well after a lot of debugging I found that the problem was here:
front = new PDJpeg(document, rescaledFrontImg);
back = new PDJpeg(document, rescaledBackImg);
The PDJpeg class has two constructors:
PDJpeg(PDDocument doc, BufferedImage bi)
PDJpeg(PDDocument doc, InputStream is)
I was passing a BufferedImage and at some point that I still can't figure out, I assume all the bytes were not being completely sent thus I got the message "Insufficient Data For An Image".
Solution: I passed an InputStream instead of a BufferedImage.
I still don't know why I got that error using a BufferedImage maybe I needed to do some sort of .push()?
This code worked for me.
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import org.apache.commons.imaging.Imaging;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
public void generatePdfFromTifPbox(File sourceFile, String destinationPath) throws Exception {
//sourceFile is tiff file, destinationPath is pdf destination path with pdf file name
PDDocument doc = new PDDocument();
List<BufferedImage> bimages = Imaging.getAllBufferedImages(sourceFile);
for (BufferedImage bi : bimages) {
PDPage page = new PDPage();
doc.addPage(page);
PDPageContentStream contentStream = new PDPageContentStream(doc, page);
try {
// the .08F can be tweaked. Go up for better quality,
// but the size of the PDF will increase
PDImageXObject image = JPEGFactory.createFromImage(doc, bi, 0.08f);
Dimension scaledDim = getScaledDimension(new Dimension(image.getWidth(), image.getHeight()),
new Dimension((int) page.getMediaBox().getWidth(), (int) page.getMediaBox().getHeight()));
contentStream.drawImage(image, 1, 1, scaledDim.width, scaledDim.height);
} finally {
contentStream.close();
}
}
doc.save(destinationPath);
}
private Dimension getScaledDimension(Dimension imgSize, Dimension boundary) {
int original_width = imgSize.width;
int original_height = imgSize.height;
int bound_width = boundary.width;
int bound_height = boundary.height;
int new_width = original_width;
int new_height = original_height;
// first check if we need to scale width
if (original_width > bound_width) {
// scale width to fit
new_width = bound_width;
// scale height to maintain aspect ratio
new_height = (new_width * original_height) / original_width;
}
// then check if we need to scale even with the new height
if (new_height > bound_height) {
// scale height to fit instead
new_height = bound_height;
// scale width to maintain aspect ratio
new_width = (new_height * original_width) / original_height;
}
return new Dimension(new_width, new_height);
}
Reference/Courtesy: http://www.paulzepernick.com/java/java-apache-pdfbox-convert-multipage-tiff-to-pdf/
Maven dependency:
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-imaging</artifactId>
<version>1.0-alpha1</version>
</dependency>

What's wrong with my java code to rotate a jpeg photo?

I simply want to enable the user of my web site to change the orientation of a submitted photo from horizontal to vertical. Here's my code:
public static final void rotatePhoto(String jpgFilename){
BufferedImage originalImage = null, newImage=null;
try{
File file = new File(jpgFilename);
originalImage = ImageIO.read(file);
System.out.println("Photo.rotatePhoto(" +jpgFilename +") originalImage.getWidth(null)=" +originalImage.getWidth(null) +" originalImage.getHeight(null)=" +originalImage.getHeight(null) );
java.awt.image.AffineTransformOp opRotated = new java.awt.image.AffineTransformOp( java.awt.geom.AffineTransform.getQuadrantRotateInstance(1), null );
newImage = opRotated.createCompatibleDestImage(originalImage, originalImage.getColorModel());
opRotated.filter(originalImage, newImage);
}catch (IOException e){
}
/// Write result to file::::::::::::::::::::::::::::::::::::::::::::::::::::
try{
File outputfile = new File(testFilename);
ImageIO.write(newImage, "jpg", outputfile);
}catch(IOException ioE){
}
}
Problem is I get this error even though the System.out.println shows the width and height to be 640x480
java.awt.image.RasterFormatException: Transformed width (0) is less than or equal to 0.
java.awt.image.AffineTransformOp.createCompatibleDestImage(AffineTransformOp.java:447)
base.Photo.rotatePhoto(Photo.java:135)
base.ProcessContent.handleInput(ProcessContent.java:245)
servlets.ProcessServlet.doPost(ProcessServlet.java:74)
servlets.ProcessServlet.doGet(ProcessServlet.java:33)
javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
Any ideas or workarounds?
Try creating a new AffineTransform from scratch and using it in your AffineTransformOp constructor:
AffineTransform tx = new AffineTransform();
tx.rotate(Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
newImage = op.filter(originalImage, newImage);
You also have to make sure newImage contains the data returned by the filter() method.
Oddly enough, this will only work when you set the formatName in ImageIO.write() to "png". I tried using jpg and the result was a black picture.
By using: AffineTransform.getQuadrantRotateInstance(1);
Your AffineTransform is rotating by a positive number of quadrants by axis. That will mess up the Transform Operation since it depends on the x and y, whenever it creates the compatible image.
int w = r.x + r.width;
int h = r.y + r.height;
if (w <= 0) {
throw new RasterFormatException("Transformed width ("+w+
") is less than or equal to 0.");
}
I would recommend doing it yourself:
public final void rotatePhoto(String jpgFilename) throws IOException {
File file = new File(jpgFilename);
BufferedImage originalImage = ImageIO.read(file);
// You could use Math.PI / 2, depends on your input.
AffineTransform affineTransform = new AffineTransform();
affineTransform.rotate(Math.toRadians(90), originalImage.getWidth() / 2, originalImage.getHeight() / 2);
// Now lets make that transform an operation, and we use it.
AffineTransformOp opRotated = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR);
BufferedImage newImage = opRotated.filter(originalImage, null);
// Save the image.
File outputfile = new File("rotated.jpg");
ImageIO.write(newImage, "jpg", outputfile);
}
UPDATE: Btw, it has been answered before on How do I write a servlet which rotates images?

Categories