PDFBox Page Rotation - Scaled Image Size Decreases - java

We are using PDFBox 2.0 version in our project. What I needed to add a QRcode image with the rotation of the page. I have taken reference from the below link to scale the content of the page after rotation. (Without this code earlier I was not able to even see the image on page)
To scale content of the page
But, after putting referenced code into place the actual image size showing decreased (By comparing the same image get displayed without rotation). Below are the two PDF page images with and without rotation.
Image_Without_Rotation
Image_With_90_Degree_Rotation
Herewith sharing the code to add image into page with rotation:
PDDocument pdDocument = PDDocument.load(new File(aBarcodeVO.getSourceFilePath()), "");
int total = pdDocument.getNumberOfPages();
PDPage page = pdDocument.getDocumentCatalog().getPages().get(0);
PDRectangle rectangle = getRotatedMediaBox(page);
PDDocument newDocument = new PDDocument();
for (int i = 0; i < total; i++) {
PDPage pdPage = newDocument.importPage(pdDocument.getPage(i));
PDRectangle pageRect = getRotatedMediaBox(pdPage);
int rotation = pdPage.getRotation();
PDRectangle cropBoxRect = page.getCropBox();
//Calculate margin between crop box rectangle and page rectangle.
float[] margins = getCropBoxMargin(pageRect, cropBoxRect, rotation);
if (rotation == 90 || rotation == 270) {
cropBoxRect = new PDRectangle(cropBoxRect.getLowerLeftY(), cropBoxRect.getLowerLeftX(), cropBoxRect.getHeight(),
cropBoxRect.getWidth());
}
//Calculate llx and lly for QR Code image.
float rectLlx = getOffsetX(displacementVO, qrImageSize) + margins[0];
float rectLly = getOffsetY(displacementVO, qrImageSize) + margins[1];
BufferedImage bufferedImage = getImage();
PDPageContentStream pageContentStream = new PDPageContentStream(newDocument, pdPage,
PDPageContentStream.AppendMode.APPEND, true);
PDImageXObject image = JPEGFactory.createFromImage(newDocument, bufferedImage);
//ADDED CODE HERE TO SCALE THE IMAGE
if (rotation == 90 || rotation == 270) {
Matrix matrix = Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0);
PDRectangle cropBox = pdPage.getCropBox();
float tx = (cropBox.getLowerLeftX() + cropBox.getUpperRightX()) / 2;
float ty = (cropBox.getLowerLeftY() + cropBox.getUpperRightY()) / 2;
Rectangle rectang = cropBox.transform(matrix).getBounds();
float scale = Math.min(cropBox.getWidth() / (float)rectang.getWidth(), cropBox.getHeight() / (float)rectang.getHeight());
pageContentStream.transform(Matrix.getTranslateInstance(tx, ty));
pageContentStream.transform(matrix);
pageContentStream.transform(Matrix.getScaleInstance(scale, scale));
pageContentStream.transform(Matrix.getTranslateInstance(-tx, -ty));
}
pageContentStream.drawImage(image, rectLlx, rectLly, qrImageSize, qrImageSize);
pageContentStream.close();
}
newDocument.save(new File(getDestinationFilePath(false)));
newDocument.close();
pdDocument.close();
Please guide me that what causing this image size decreasing or tell me if I have need to change approach for page rotation.

Related

Retrieve the coordinates of a Paragraph in iText

I'm trying to retrieve the x, y coordinates from a Paragraph created in iText. I followed the approved answer in How to get vertical cursor position when writing document in iText 7? but I'm not getting the expected result.
PdfDocument pdfDoc = new PdfDocument(new PdfWriter("output/ITextSandbox/Coordinates.pdf"));
pdfDoc.setDefaultPageSize(PageSize.LETTER); // 8.5 x 11
Document document = new Document(pdfDoc);
PdfFont font = PdfFontFactory.createFont(StandardFonts.COURIER, PdfEncodings.UTF8);
document.setFont(font);
document.setFontSize(10);
Paragraph paragraph = null;
// Print 5 lines to ensure the y coord is sufficiently moved away from the top of the page.
for (int i = 1; i <= 5; i++)
{
paragraph = new Paragraph();
paragraph.add(new Text("Line " + i));
document.add(paragraph);
}
// Print a new paragraph from which to obtain the x, y coordinates.
paragraph = new Paragraph();
paragraph.add(new Text("Line 6"));
document.add(paragraph);
// Follow the steps from the approved answer in
// https://stackoverflow.com/questions/51953723/how-to-get-vertical-cursor-position-when-writing-document-in-itext-7
IRenderer renderer = paragraph.createRendererSubTree().setParent(document.getRenderer());
float width = document.getPageEffectiveArea(PageSize.LETTER).getWidth();
float height = document.getPageEffectiveArea(PageSize.LETTER).getHeight();
LayoutResult layoutResult = renderer.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width, height))));
float y = layoutResult.getOccupiedArea().getBBox().getY();
float x = layoutResult.getOccupiedArea().getBBox().getX();
System.out.println("x = " + x + ", y = " + y); // y should be approximately 630, not 710.
With standard margins and 10 pt font, the coordinates for the 6th line should approximately be x = 0, y = 630. Instead, I get y = 710.
The code sample in the question simulates rendering on a certain layout area. That approach works to determine the coordinates on the page if the exact same elements are simulated on the exact same layout area.
In the question, only the 6th paragraph is simulated, so the larger y coordinate (higher on the page) is expected. To get the correct y coordinate, the 5 preceding paragraphs would also have to be simulated.
Moreover, the layout area is not the same as page. This code does not take into account the page margins correctly:
float width = document.getPageEffectiveArea(PageSize.LETTER).getWidth();
float height = document.getPageEffectiveArea(PageSize.LETTER).getHeight();
LayoutResult layoutResult =
renderer.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width, height))));
With the default page margins of 36, the correct layout area would be:
float width = document.getPageEffectiveArea(PageSize.LETTER).getWidth();
float height = document.getPageEffectiveArea(PageSize.LETTER).getHeight();
LayoutResult layoutResult =
renderer.layout(new LayoutContext(new LayoutArea(1, new Rectangle(36, 36, width, height))));
A much easier way to get the current coordinates after rendering some elements is:
/*...*/
paragraph = new Paragraph();
paragraph.add(new Text("Line 6"));
document.add(paragraph);
Rectangle remaining = document.getRenderer().getCurrentArea().getBBox();
float y = remaining.getTop();
System.out.println("y = " + y);
Result: y = 631.6011
To illustrate the remaining layout area, let's draw it on the page:
PdfCanvas canvas = new PdfCanvas(pdfDoc.getPage(1));
canvas.setStrokeColor(ColorConstants.RED).rectangle(remaining).stroke();
With some different page margins:
document.setMargins(5, 25, 15, 5);

Rotate watermark text at 45 degree angle across the center Apache PDFBox

I want to add a text to the PDF using PDFBox API and rotate it by 45 Degree and place it at the center of the page, The text is dynamic and should be placed in the center always, I got everything else to work except centering piece, I'll appreciate any help.
I have this code:
Point2D.Float pageCenter = getCenter(page);
float stringWidth = getStringWidth(watermarkText, font, fontSize);
float textX = pageCenter.x - stringWidth / 2F + center.x;
System.out.println(textX);
float textY = pageCenter.y + center.y;
//System.out.println("Inside cross"+textX+", "+textY);
fontSize = 110.0f;
cs.transform(Matrix.getRotateInstance(Math.toRadians(45), textX, textY));
cs.moveTo(0, 0);
cs.lineTo(125, 0);
r0.setNonStrokingAlphaConstant(0.20f);
This is the result i want:
Output PDF
What I do is to first rotate based on the calculated angle. In this "rotated world" I do a horizontal offset so that the text is in the middle, and also move the text vertically a bit lower, so that it is in the "vertical" middle of an imagined diagonal line (horizontal in the "rotated world").
try (PDDocument doc = new PDDocument())
{
PDPage page = new PDPage();
doc.addPage(page);
PDFont font = PDType1Font.HELVETICA_BOLD;
try (PDPageContentStream cs =
new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true))
// use this long constructor when working on existing PDFs
{
float fontHeight = 110;
String text = "Watermark";
float width = page.getMediaBox().getWidth();
float height = page.getMediaBox().getHeight();
int rotation = page.getRotation();
switch (rotation)
{
case 90:
width = page.getMediaBox().getHeight();
height = page.getMediaBox().getWidth();
cs.transform(Matrix.getRotateInstance(Math.toRadians(90), height, 0));
break;
case 180:
cs.transform(Matrix.getRotateInstance(Math.toRadians(180), width, height));
break;
case 270:
width = page.getMediaBox().getHeight();
height = page.getMediaBox().getWidth();
cs.transform(Matrix.getRotateInstance(Math.toRadians(270), 0, width));
break;
default:
break;
}
float stringWidth = font.getStringWidth(text) / 1000 * fontHeight;
float diagonalLength = (float) Math.sqrt(width * width + height * height);
float angle = (float) Math.atan2(height, width);
float x = (diagonalLength - stringWidth) / 2; // "horizontal" position in rotated world
float y = -fontHeight / 4; // 4 is a trial-and-error thing, this lowers the text a bit
cs.transform(Matrix.getRotateInstance(angle, 0, 0));
cs.setFont(font, fontHeight);
//cs.setRenderingMode(RenderingMode.STROKE); // for "hollow" effect
PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
gs.setNonStrokingAlphaConstant(0.2f);
gs.setStrokingAlphaConstant(0.2f);
gs.setBlendMode(BlendMode.MULTIPLY);
cs.setGraphicsStateParameters(gs);
// some API weirdness here. When int, range is 0..255.
// when float, this would be 0..1f
cs.setNonStrokingColor(255, 0, 0);
cs.setStrokingColor(255, 0, 0);
cs.beginText();
cs.newLineAtOffset(x, y);
cs.showText(text);
cs.endText();
}
doc.save("watermarked.pdf");
}
Note that I've set both stroking and non stroking (= fill). This is useful for people who want to try the (disabled) "hollow" appearance, that one uses stroking only. The default mode is fill, i.e. non-stroking.

Scaling down and making image square in android

I need a way to scale an image down to 78x78. I have found ways of doing this by cutting part of the image off, like this:
Bitmap image = Bitmap.createBitmap(image, 0, 0, 78, 78);
but I need to maintain as much of the image as possible. I had thought of scaling the image down and then making it square:
Bitmap image = Bitmap.createScaledBitmap(imageTest, 78, 78, true);
but of course this creates a square image that is squashed.
Can anyone suggest how I can create a 78x78 image that doesn't rescale and maintains as much of the original image as possible?
From what I understood, you should scale down and center crop the image. Try this code out.
public Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) {
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
// Compute the scaling factors to fit the new height and width, respectively.
// To cover the final image, the final scaling will be the bigger
// of these two.
float xScale = (float) newWidth / sourceWidth;
float yScale = (float) newHeight / sourceHeight;
float scale = Math.max(xScale, yScale);
// Now get the size of the source bitmap when scaled
float scaledWidth = scale * sourceWidth;
float scaledHeight = scale * sourceHeight;
// Let's find out the upper left coordinates if the scaled bitmap
// should be centered in the new size give by the parameters
float left = (newWidth - scaledWidth) / 2;
float top = (newHeight - scaledHeight) / 2;
// The target rectangle for the new, scaled version of the source bitmap will now
// be
RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
// Finally, we create a new bitmap of the specified size and draw our new,
// scaled bitmap onto it.
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
Canvas canvas = new Canvas(dest);
canvas.drawBitmap(source, null, targetRect, null);
return dest;
}
Hope it helps
Try this:
Bitmap image = Bitmap.createScaledBitmap(testImage, (int) 78 * (testImage.getWidth() / testImage.getHeight()), 78, true);
image = Bitmap.createBitmap(image, (int) (image.getWidth() - 78) / 2, 78);
Haven't tested this, as I'm on my way to bed, but it should accomplish what you want, so long as your image has a width greater than or equal to its height.
Regardless, I'd suggest you use BufferedImage instead of Bitmap.
The idea here would be resize your image using the same resize rate for width and height keeping the smaller size in 78. After that you can use a center point based crop to get the middle of your image and making it a squared image.
Image srcImage;
int widthSrc = 150;
int heightSrc = 180;
float resizeRate = 78 / min(widthSrc, heightSrc);
Image resizedImage = resizeImage($srcImage, resizeRate);
int widthDest = 78;
int heightDest = 78;
int cropX = ($widthSrc - $widthDest)/2;
int cropY = ($heightSrc - $heightDest)/2;
Image croppedImage = cropImage(resizedImage,$widthDest, $heightDest, $cropX, $cropY);
If the image is already square you can skip the crop part.

Java document image scaling and rotation

I have document images of varying dimensions and I want to be able to efficiently scale and rotate them in the following manner (standard "Rotate" and "Zoom" logic). How do I do it?
An image is H pixels high and W pixels wide. Initially, it should scale to 600 pixels wide. On each rotation, the panel's width and height should swap and the scaled image should rotate 90 degrees. On each zoom, the image should scale by factor "scale".
Here's what I've tried so far on BufferedImage img... the resulting BufferedImage scales and rotates but does not translate (to be centered atop the panel after a 90-degree rotation):
double scale = zoom * 600.0 / img.getWidth();
rotation = (rotation + degrees) % 360;
int scaledWidth = (int)(scale * img.getWidth());
int scaledHeight = (int)(scale * img.getHeight());
BufferedImage bufferedImage = new BufferedImage(scaledWidth, scaledHeight, img.getType());
if (rotation % 180 == 0)
bufferedImage = new BufferedImage(scaledWidth, scaledHeight, img.getType());
else
bufferedImage = new BufferedImage(scaledHeight, scaledWidth, img.getType());
AffineTransform transform = AffineTransform.getRotateInstance(Math.toRadians(rotation), scaledWidth/2, scaledHeight/2);
transform.scale(scale, scale);
AffineTransformOp operation = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
scaledImage = operation.filter(img, bufferedImage);
imagePanel.setPreferredSize(new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight()));
Aha! The key (the JavaDoc was confusing) was realizing that on AffineTransform, rotate() and other methods transform the matrix, not the image! The following code works automagically!
/**
* Transforms the image efficiently without losing image quality.
* Scales the image to a width of (600 * scale) pixels, rotates the image,
* and translates (moves) the image to recenter it if rotated 90 or 270 degrees.
*/
protected BufferedImage transformImage(BufferedImage image)
{
int scaledWidth = (int)(scale * image.getWidth());
int scaledHeight = (int)(scale * image.getHeight());
// Methods AffineTransform.rotate(), AffineTransform.scale() and AffineTransform.translate()
// transform AffineTransform's transformation matrix to multiply with the buffered image.
// Therefore those methods are called in a counterintuitive sequence.
AffineTransform transform;
if (rotation % 180 == 0)
{
// First scale and second rotate image
transform = AffineTransform.getRotateInstance(Math.toRadians(rotation), scaledWidth/2, scaledHeight/2);
transform.scale(scale, scale);
}
else
{
// First scale, second rotate, and third translate image
transform = AffineTransform.getTranslateInstance((scaledHeight-scaledWidth)/2, (scaledWidth-scaledHeight)/2);
transform.rotate(Math.toRadians(rotation), scaledWidth/2, scaledHeight/2);
transform.scale(scale, scale);
}
AffineTransformOp operation = new AffineTransformOp(transform, AffineTransformOp.TYPE_BICUBIC);
BufferedImage transformedImage = operation.createCompatibleDestImage(image, image.getColorModel());
return operation.filter(image, transformedImage);
}

Rotate Buffered Image (diagonal Text)

(Sorry for the links, I'm new and I cannot post images)
I want to accomplish the following : create a table with the legends on the top, and in a diagonal way.
but I'm having some problems, I have the following image, and I'm trying to rotate it 45º (the result it's at the right),
Here is my code:
//just some labels
ArrayList<String> labels = new ArrayList<String>();
labels.add("Juan");
labels.add("QWERTYYY");
labels.add("ANA");
// margin
int margin=3;
//diagonal = 45º
// value to shift each label
int diagonalShift = (int)(cellSizeWidth / Math.sqrt(2d));
// height, width represent the size of the final image
// heightSub, widthSub represent the size of the image to be rotated taking into account the shift for each label
int widthSub = height + (diagonalShift * labels.size());
int heightSub = width;
// image to Display
BufferedImage image = new BufferedImage(height, width, BufferedImage.TYPE_INT_RGB);
Graphics2D imageGraphics = (Graphics2D) image.getGraphics();
// tempImage: subImage to rotate and place in image
BufferedImage tempImage = new BufferedImage(widthSub, heightSub, BufferedImage.TYPE_INT_RGB);
Graphics2D tempImageGraphics = (Graphics2D) tempImage.getGraphics();
tempImageGraphics.setColor(Color.BLUE);
tempImageGraphics.drawRect(0, 0, widthSub-1, heightSub-1);
// I'd like to use antialias, but it's giving bad results
// tempImageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
// drawing labels
// as we're designing a table cellSizeWidth and CellSizeHeight represent the dimensions for each cell
tempImageGraphics.setColor(Color.WHITE);
for (int i = 0; i < labels.size(); i++) {
String label = labels.get(i);
tempImageGraphics.drawString(label,
margin + (i * diagonalShift),
(int) (i * cellSizeWidth) + fontSize + centerDistance);
}
I tried the following:
//rotating
AffineTransform fontAfineTransform = new AffineTransform();
// fontAfineTransform.rotate(verticalTextDirection.rotationAngle());
which gives as result the image at the right in the second Image 2
so I need to apply a translation to get it to the right position
// Math.sqrt(2d) because I'm working with 45º and the height becomes the hypotenuse
// fontAfineTransform.translate(-height/Math.sqrt(2d),height/Math.sqrt(2d));
//drawing into image
imageGraphics.drawImage(tempImage, fontAfineTransform, null);
can someone please explain how the affineTransform works, or how can I get the text to be in a diagonal way.
Thanks

Categories