I'm troubleshooting an issue where drawing an image onto a PDF document using the PDFBox library seems to take an excessive amount of time (compared to PHP where it's almost instantly). The code in question first creates a bar/qr code using Google com.google.zxing library and then adds that to the PDF document in question. Here are the methods in question:
Create the Barcode (one of the examples):
private static byte[] generateBarCode(String barcodeText, int width, int height, Writer writer, BarcodeFormat barcodeFormat) throws IOException, WriterException {
if (Objects.isNull(barcodeText) || width <= 0 || height <= 0) {
throw new IllegalArgumentException(String.format("generateBarCode: wrong input values %s %s %s %s", barcodeText, width, height, barcodeFormat.name()));
}
Map<EncodeHintType, Object> hintMap = new EnumMap<>(EncodeHintType.class);
hintMap.put(EncodeHintType.MARGIN, 0);
BitMatrix bitMatrix = writer.encode(barcodeText, barcodeFormat, width, height, hintMap);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "png", bos);
bos.close();
return bos.toByteArray();
}
This is then passed to the below method as the byte[] image.
Draw the image on the PDF Document:
public static void drawImage(PDPageContentStream contentStream, int x, int y, byte[] image, PDDocument document, Integer width, Integer height) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(image);
BufferedImage bim = ImageIO.read(bais);
PDImageXObject pdImage = LosslessFactory.createFromImage(document, bim);
if (width != null && height != null) { // if we set the image width/height
contentStream.drawImage(pdImage, x, y, width, height);
return;
}
contentStream.drawImage(pdImage, x, y);
}
At time we need to create a PDF with 100s of pages and each page/item will have a different barcode which needs to be created and then drawn, along with a bunch of text and other non-image items. This can take upwards of 10+ minutes for a 1000 page document for example, however if I comment out the barcode generation and just draw an existing image from my classpath it can complete in ~60 seconds (which I still think is too long). If I comment out both then the 1000 document PDF basically gets rendered instantly from the template and adding all my text.
Is there a better way of doing this that I am overlooking?
Related
I am trying to make a bubble app that takes screenshot of other apps.
I found this project link with a media projection sample but the images are not been saved to the device.
is there a way i can save this image to the device or is there any other way i can take a screenshot with my app in a background service.
I have checked the link and I think there's a way to save the file taken from screenshot.
From ImageTransmogrifier class
#Override
public void onImageAvailable(ImageReader reader) {
final Image image=imageReader.acquireLatestImage();
if (image!=null) {
Image.Plane[] planes=image.getPlanes();
ByteBuffer buffer=planes[0].getBuffer();
int pixelStride=planes[0].getPixelStride();
int rowStride=planes[0].getRowStride();
int rowPadding=rowStride - pixelStride * width;
int bitmapWidth=width + rowPadding / pixelStride;
if (latestBitmap == null ||
latestBitmap.getWidth() != bitmapWidth ||
latestBitmap.getHeight() != height) {
if (latestBitmap != null) {
latestBitmap.recycle();
}
latestBitmap=Bitmap.createBitmap(bitmapWidth,
height, Bitmap.Config.ARGB_8888);
}
latestBitmap.copyPixelsFromBuffer(buffer);
image.close();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
Bitmap cropped=Bitmap.createBitmap(latestBitmap, 0, 0,
width, height);
cropped.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] newPng=baos.toByteArray();
svc.processImage(newPng);
}
}
as you can notice there's a latestBitmap object there, from here you can actually save this bitmap to whatever place you want to save.
E.g. you can refer to this link https://www.simplifiedcoding.net/android-save-bitmap-to-gallery/ to save it on your Gallery :)
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.
I'm using iText for stamping a watermark (text: "SuperEasy You Done") on PDF files as described in How to watermark PDFs using text or images? (TransparentWatermark2.java). See project source code on GitHub.
Now an example of the PDF I'm getting is this one (the rest of the document is omitted):
As you can see the watermark is centered and horizontal.
I'd like to keep it centered in the middle of the page, but rotate it "45" degrees, so it rotates anticlockwise. Something like this:
This is the code for stamping the watermark on a given byte array (pdf documents only for me right now)
/**
* Returns the same document with the watermark stamped on it.
* #param documentBytes Byte array of the pdf which is going to be returned with the watermark
* #return byte[] with the same byte array provided but now with the watermark stamped on it.
* #throws IOException If any IO exception occurs while adding the watermark
* #throws DocumentException If any DocumentException exception occurs while adding the watermark
*/
private byte[] getDocumentWithWaterMark(byte[] documentBytes) throws IOException, DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// pdf
PdfReader reader = new PdfReader(documentBytes);
int n = reader.getNumberOfPages();
PdfStamper stamper = new PdfStamper(reader, outputStream);
// text watermark
Font font = new Font(Font.HELVETICA, 60);
Phrase phrase = new Phrase("SuperEasy You Done", font);
// transparency
PdfGState gs1 = new PdfGState();
gs1.setFillOpacity(0.06f);
// properties
PdfContentByte over;
Rectangle pagesize;
float x, y;
// loop over every page (in case more than one page)
for (int i = 1; i <= n; i++) {
pagesize = reader.getPageSizeWithRotation(i);
x = (pagesize.getLeft() + pagesize.getRight()) / 2;
y = (pagesize.getTop() + pagesize.getBottom()) / 2;
over = stamper.getOverContent(i);
over.saveState();
over.setGState(gs1);
// add text
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, phrase, x, y, 0);
over.restoreState();
}
stamper.close();
reader.close();
return outputStream.toByteArray();
}
PS: I read this, but it didn't help:
http://itext.2136553.n4.nabble.com/rotate-a-watermark-td2155042.html
You just need to specify the desired rotation angle as the 6th parameter in this line:
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, phrase, x, y, 0); // rotate 0 grades in this case
If the specified value is positive ( > 0) the rotation is anticlockwise, otherwise (< 0) the rotation is clockwise.
In this particular case, for rotating the watermark 45 degrees anticlockwise you just need to write the previous line like this:
ColumnText.showTextAligned(over, Element.ALIGN_CENTER, phrase, x, y, 45f); // 45f means rotate the watermark 45 degrees anticlockwise
By applying this same principle we can achieve any rotation in any direction.
The whole documentation is here: https://itextpdf.com/en/resources/api-documentation under the links for version 5 and version 7.
I'm currently using Apache POI 3.16 to take a PPTX slide deck and transform it into a set of .png thumbnails. I modeled my PPTX to PNG function closely off of a few pre-made solutions I found across the web.
I'm having issues primarily with text being rendered about 6-8pt larger in the resulting PNGs. Additionally, complex shapes such as hexagons are extremely skewed when being rendered. Can anyone suggest any fixes? I'm mostly concerned with the font sizes being much larger.
Here is my code, commented for readability:
public List<String> generatePNGThumbnails(String inputFilePath) {
// List of the thumbnail paths that will be returned.
ArrayList<String> thumbnailFilePaths = new ArrayList<>();
// Open the file.
File inputFile = new File(inputFilePath);
try {
// Open the PPTX as an XMLSlideShow.
XMLSlideShow slideShow = new XMLSlideShow(OPCPackage.open(inputFile));
// Output size for rendered images.
Dimension pageSize = slideShow.getPageSize();
int width = (int)(pageSize.width * TRANSFORMATION_SCALE);
int height = (int)(pageSize.height * TRANSFORMATION_SCALE);
// Get a list of the slides in the PPTX.
List<XSLFSlide> slides = slideShow.getSlides();
for (int i = 0; i < slides.size(); i++) {
XSLFSlide slide = slides.get(i);
// Create a new image object and prepare it for rendering.
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
// Set graphics rendering options and draw the PPTX slide to the image.
graphics.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
graphics.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY
);
graphics.setRenderingHint(
RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_QUALITY
);
graphics.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC
);
graphics.setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON
);
graphics.setRenderingHint(
RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY
);
graphics.setColor(Color.white);
graphics.clearRect(0, 0, width, height);
graphics.scale(TRANSFORMATION_SCALE, TRANSFORMATION_SCALE);
// Draw the slide to the 2DGraphics.
slide.draw(graphics);
// Save the newly created image to the local disk.
String slideFileName = "slide" + i + ".pptx";
FileOutputStream fileOutputStream = new FileOutputStream(slideFileName);
ImageIO.write(img, "png", fileOutputStream);
// Add the output file path to the list of paths to be returned.
thumbnailFilePaths.add(slideFileName);
// Clean up before proceeding to the next slide.
graphics.dispose();
img.flush();
fileOutputStream.close();
}
} catch (InvalidFormatException | IOException e) {
// ...
}
}
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>