Creating a 3D shadow effect on images with itextpdf - java

I want to create a 3D shadow effect on images that I place in a pdf. I am using itextpdf.
The question is similar to :
Adding shadow effect on iText elements
but with images and not texts.
My images are placed in table cells. As long as the table is not completed, no way to get the actual size of the image nor its coordinates in the page, this makes it tricky.
Any brilliant id ?
Thanks and regards,
Sylvain

4 hours later, I found a way to do it using PdfCellEvent and drawing what I need inside the cell's padding.
static class ShadowRectangle implements PdfPCellEvent {
public void cellLayout(PdfPCell cell, Rectangle rect,
PdfContentByte[] canvas) {
PdfContentByte lCb = canvas[PdfPTable.LINECANVAS];
// Paddings for the border
int paddingHorizontal = 8;
int paddingVertical = 8;
// Width of the shadow
int shadowwidth = 7;
// Calculate border location and size
float left = rect.getLeft() + paddingHorizontal;
float bottom = rect.getBottom() + paddingVertical;
float width = rect.getWidth() - 2 * paddingHorizontal;
float height = rect.getHeight() - 2 * paddingVertical;
lCb.saveState();
lCb.setColorFill(BaseColor.GRAY);
// Draw the shadow at the bottom
lCb.rectangle(left + shadowwidth, bottom - shadowwidth, width, shadowwidth);
lCb.fill();
// Draw the shadow at the right
lCb.rectangle(left + width, bottom - shadowwidth, shadowwidth, height);
lCb.fill();
//lCb.setColorStroke(BaseColor.RED);
// Draw the border
//lCb.rectangle(left, bottom, width, height);
lCb.stroke();
lCb.restoreState();
}
}
// And I fill the PdfPTable this way
PdfPTable lImages = new PdfPTable(NB_PICTURE_PER_ROW);
lImages.getDefaultCell().setBorder(Rectangle.NO_BORDER);
// ... in a loop
lImageBoucle = Image.getInstance(lFile.getCanonicalPath() + File.separator + lTabPhotos[i]);
PdfPCell lCell = new PdfPCell();
lCell.setImage(lImageBoucle);
lCell.setBorder(Rectangle.NO_BORDER);
lCell.setPadding(8);
PdfPCellEvent lShadowRectangle = new ShadowRectangle();
lCell.setCellEvent(lShadowRectangle);
lImages.addCell(lCell);

Related

How to define an offset for a PatternColor fill in iText?

I am trying to add tiled diagonal watermarks to the pdf, but it seems that pattern fills in iText are always tiled from the bottom left of the page, meaning that the tiles at the top and right side of the page can be cut abruptly. Is there an option to tile from the top left or with an offset instead?
Here is a sample of the code:
List<String> watermarkLines = getWatermarkLines();
Rectangle watermarkRect = getWatermarkRect();
PdfContentByte over = stamper.getOverContent(1);
PdfPatternPainter painter = over.createPattern(watermarkRect.getWidth(), watermarkRect.getHeight();
for (int x = 0; x < watermarkLines.size(); x++) {
AffineTransform trans = getWatermarkTransform(watermarkLines, x);
ColumnText.showTextAligned(painter, 0, watermarkLines.get(x), (float) trans.getTranslateX(), (float) trans.getTranslateY(), 45f);
}
over.setColorFill(new PatternColor(painter));
over.rectangle(0, 0, pageSize.getWidth(), pageSize.getHeight());
over.fill();
I tried changing the x and y of the rectangle function to negative or positive values, but it seems that the watermark is still stamped in the pattern as if it was tiled from the bottom left, cutting it in the same place as before.
First of, I cannot fathom which iText version you are using,
List<String> watermarkLines = getWatermarkLines();
...
ColumnText.showTextAligned(painter, 0, watermarkLines.get(x), (float) trans.getTranslateX(), (float) trans.getTranslateY(), 45f);
implies that the third parameter of the ColumnText.showTextAligned method you use is typed as String or Object. The iText 5 version I have at hand, though, requires a Phrase there. Below I'll show how to apply an offset with the current iText 5.5.13. You'll have to check whether it also works for your version.
Yes, you can apply an offset... in the pattern definition!
If instead of
PdfPatternPainter painter = over.createPattern(watermarkRect.getWidth(), watermarkRect.getHeight());
you create the pattern like this
PdfPatternPainter painter = over.createPattern(2 * watermarkRect.getWidth(), 2 * watermarkRect.getHeight(),
watermarkRect.getWidth(), watermarkRect.getHeight());
you have the same step size of pattern application (watermarkRect.getWidth(), watermarkRect.getHeight()) but a canvas twice that width and twice that height to position you text on. By positioning the text with an offset, you effectively move the whole pattern by that offset.
E.g. if you calculate the offsets as
Rectangle pageSize = pdfReader.getCropBox(1);
float xOff = pageSize.getLeft();
float yOff = pageSize.getBottom() + ((int)pageSize.getHeight()) % ((int)watermarkRect.getHeight());
and draw the text using
ColumnText.showTextAligned(painter, 0, new Phrase(watermarkLines.get(x)), (float) trans.getTranslateX() + xOff, (float) trans.getTranslateY() + yOff, 45f);
the pattern should fill the page as if starting at the top left corner of the visible page.
You haven't supplied getWatermarkLines, getWatermarkRect, and getWatermarkTransform. If I use
static AffineTransform getWatermarkTransform(List<String> watermarkLines, int x) {
return AffineTransform.getTranslateInstance(6 + 15*x, 6);
}
static Rectangle getWatermarkRect() {
return new Rectangle(65, 50);
}
static List<String> getWatermarkLines() {
return Arrays.asList("Test line 1", "Test line 2");
}
your original code for me creates a top left corner like this
and the code with the above offset creates one like this

Can I tint texture inside an actor?

I need to tint an item in my ShopCell (it should be black at first, and after purchase it becomes normal).
public class ShopCell extends Group {
private final float WIDTH = 100;
private final float HEIGHT = 150;
private Label label;
public ShopCell(TextureRegion itemTxt, int price) {
this.setSize(WIDTH, HEIGHT);
Image cellBg = new Image(Resource.cellBg);
cellBg.setSize(WIDTH, HEIGHT);
Image item = new Image(itemTxt);
float aspectRatio = item.getWidth()/item.getHeight();
item.setBounds(cellBg.getX() + WIDTH/2-(HEIGHT*0.3f*aspectRatio), cellBg.getY() + HEIGHT/3, HEIGHT*0.6f*aspectRatio, HEIGHT*0.6f);
label = new Label(String.valueOf(price), new Label.LabelStyle(Resource.font, Color.YELLOW));
label.setPosition(cellBg.getX() + WIDTH/2 - label.getWidth()/2, cellBg.getY() + HEIGHT/15);
this.addActor(cellBg);
this.addActor(item);
this.addActor(label);
}
I've tried to make Sprite at first, tint it like this and add it to Image:
Sprite sprite = new Sprite(itemTxt);
sprite.setColor(Color.BLACK);
Image item = new Image(sprite);
But when I do so, my texture doesn't tint.
How can I tint it and add it to the Group? And can I return its normal appearence then (for purchased items)?
Sprite class having own color so that can be tint but when you create Image from that Sprite, only TextureRegion of Sprite used for construction of new Image object.
Set color of Image instead of tinted Sprite :
Image item = new Image(itemTxt);
item.setColor(Color.BLACK);
You can also try fadeIn/Out Action on your object
image.addAction(Actions.fadeOut(2));

How to rotate around the image center by itext?

double degPi = degrees * Math.PI / 180;
double a = Math.cos(degPi)*tImgCover.getScaledHeight();
double b = Math.sin(degPi)*tImgCover.getScaledWidth();
double c = -Math.sin(degPi) * tImgCover.getScaledHeight();
double d = Math.cos(degPi)* tImgCover.getScaledWidth();
double e = absX;
double f = absY;
contentByte.addImage(imgae, a, b, c, d, e, f);/*add image*/
How to rotate around the image center by itext?
If we have an Image image and coordinates x, y, we can draw the image without rotation with its lower left corner at the given coordinates like this
contentByte.addImage(image, image.getWidth(), 0, 0, image.getHeight(), x, y);
A bitmap image from the resources has a size of 1x1 with the coordinate origin at its lower left. Thus, this operation stretches the image to its correct size and moves it so its lower left is at the given coordinates.
If we want to draw the same image as if the one drawn above was rotated around its center by an angle rotate, therefore, we can do this by moving the 1x1 image so that the origin is in its center, stretch it to its correct size, rotate it, and then move the origin (which still is at the center of the rotated image) to the center of the unrotated image. These operations are easier to express using AffineTransform instances (from package com.itextpdf.awt.geom) instead number tupels. Thus:
// Draw image as if the previous image was rotated around its center
// Image starts out being 1x1 with origin in lower left
// Move origin to center of image
AffineTransform A = AffineTransform.getTranslateInstance(-0.5, -0.5);
// Stretch it to its dimensions
AffineTransform B = AffineTransform.getScaleInstance(image.getWidth(), image.getHeight());
// Rotate it
AffineTransform C = AffineTransform.getRotateInstance(rotate);
// Move it to have the same center as above
AffineTransform D = AffineTransform.getTranslateInstance(x + image.getWidth()/2, y + image.getHeight()/2);
// Concatenate
AffineTransform M = (AffineTransform) A.clone();
M.preConcatenate(B);
M.preConcatenate(C);
M.preConcatenate(D);
//Draw
contentByte.addImage(image, M);
(AddRotatedImage.java test method testAddRotatedImage)
For example drawing both images using
int x = 200;
int y = 300;
float rotate = (float) Math.PI / 3;
results in something like this:
With a Flip
The OP asked in a comment
how to add rotate and flip image?
For this you simply insert a mirroring affine transformation into the sequence of transformations above.
Unfortunately the OP did not mention which he meant a horizontal or a vertical flip. But as changing the rotation angle accordingly transforms one in the other, that isn't really necessary, either.
// Draw image as if the previous image was flipped and rotated around its center
// Image starts out being 1x1 with origin in lower left
// Move origin to center of image
AffineTransform A = AffineTransform.getTranslateInstance(-0.5, -0.5);
// Flip it horizontally
AffineTransform B = new AffineTransform(-1, 0, 0, 1, 0, 0);
// Stretch it to its dimensions
AffineTransform C = AffineTransform.getScaleInstance(image.getWidth(), image.getHeight());
// Rotate it
AffineTransform D = AffineTransform.getRotateInstance(rotate);
// Move it to have the same center as above
AffineTransform E = AffineTransform.getTranslateInstance(x + image.getWidth()/2, y + image.getHeight()/2);
// Concatenate
AffineTransform M = (AffineTransform) A.clone();
M.preConcatenate(B);
M.preConcatenate(C);
M.preConcatenate(D);
M.preConcatenate(E);
//Draw
contentByte.addImage(image, M);
(AddRotatedImage.java test method testAddRotatedFlippedImage)
The result with the same image as above:
With Interpolation
The OP asked in a yet another comment
How anti aliasing ?
The iText Image class knows an Interpolation property. By setting it to true (before adding the image to the document, obviously),
image.setInterpolation(true);
low resolution images are subject to interpolation when drawn.
E.g. using a 2x2 image with differently colored pixels instead of the image of Willi, you get the following results, first without interpolation, then with interpolation:
Confer the AddRotatedImage.java test testAddRotatedInterpolatedImage which adds this image:
Beware: iText Image property Interpolation effectively sets the Interpolate entry in the PDF image dictionary. The PDF specification notes in this context:
NOTE A conforming Reader may choose to not implement this feature of PDF, or may use any specific implementation of interpolation that it wishes.
Thus, on some viewers interpolation may occur differently than in your viewer, maybe even not at all. If you need a specific kind of interpolation on every viewer, upscale the image with the desired amount of interpolation / anti-aliasing before loading it into an iText Image.
public static BufferedImage rotateClockwise90( BufferedImage inputImage ){
int width = inputImage.getWidth();
int height = inputImage.getHeight();
BufferedImage returnImage = new BufferedImage( height, width , inputImage.getType() );
for( int x = 0; x < width; x++ ) {
for( int y = 0; y < height; y++ ) {
returnImage.setRGB( height-y-1, x, inputImage.getRGB( x, y ) );
}
}
return returnImage;
}

Can someone please explain iTexts canvas drawing order?

I try to draw some nested tables in iText as i thought it would be the easiest way to position everything.
So i have multiple tables inside another table who all have background color and/or strokes (via PdfPCellEvents). Unfortunately the strokes of the outer table are overlapping the background of the inner table.
I assume that comes from a wrong order of applying or some wrong set saveState or restoreState in my PdfPCellEvents.
Can anyone explain the right usage of saveState and restoreState to me and give me a hint how to apply backgrounds and strokes the right way?
Here is my code for adding a striped background cell:
PdfPCell scaleBackground = new PdfPCell();
scaleBackground.setBorder(Rectangle.NO_BORDER);
scaleBackground.setVerticalAlignment(Element.ALIGN_TOP);
scaleBackground.setCellEvent(new StripedScaleBackground(max, scaleHeight));
cellLayout method of StripedScaleBackground:
public void cellLayout(PdfPCell cell, Rectangle rect, PdfContentByte[] canvases)
{
PdfContentByte canvas = canvases[PdfPTable.LINECANVAS];
float llx = rect.getLeft();
float lly = rect.getBottom();
float urx = rect.getRight();
float ury = rect.getTop();
// Light scale lines with padding from left
canvas.setLineWidth(Constants.BORDER_WIDTH_THIN);
canvas.setColorStroke(Colors.LIGHT_GRAY);
float paddingLeft = 22f;
for (int i = 0; i <= this.maxValue; i++)
{
canvas.moveTo(llx + paddingLeft, lly + (this.scaleHeight * (i + 1)));
canvas.lineTo(urx, lly + (this.scaleHeight * (i + 1)));
}
// Vertical line
canvas.moveTo(llx + (((urx - llx) + paddingLeft) / 2), ury);
canvas.lineTo(llx + (((urx - llx) + paddingLeft) / 2), lly);
canvas.stroke();
// Fat line left and right
canvas.moveTo(llx, ury);
canvas.lineTo(llx, lly);
canvas.moveTo(urx, ury);
canvas.lineTo(urx, lly);
canvas.setLineWidth(0.8f);
canvas.setColorStroke(Colors.MEDIUM_GRAY);
canvas.stroke();
canvas.saveState();
canvas.restoreState();
}
The bar charts are tables where each cell has a cell event for gradient and border. The bar charts are added to the scaleBackground PdfPCell of the first piece of code and have following PdfPCellEvents (example of black part of the chart):
public void cellLayout(PdfPCell cell, Rectangle rect, PdfContentByte[] canvases)
{
PdfContentByte backgroundCanvas = canvases[PdfPTable.BACKGROUNDCANVAS];
float llx = rect.getLeft();
float lly = rect.getBottom();
float urx = rect.getRight();
float ury = rect.getTop();
// Draw background
// Define shading with direction and color
PdfShading shading = PdfShading.simpleAxial(this.writer,
llx, ury,
llx, lly,
Colors.BAR_CHART_BLACK_LIGHT, Colors.BAR_CHART_BLACK_DARK);
PdfShadingPattern pattern = new PdfShadingPattern(shading);
backgroundCanvas.setShadingFill(pattern);
// Draw shape with defined shading
backgroundCanvas.moveTo(llx, ury);
backgroundCanvas.lineTo(llx, lly);
backgroundCanvas.lineTo(urx, lly);
backgroundCanvas.lineTo(urx, ury);
backgroundCanvas.lineTo(llx, ury);
backgroundCanvas.fill();
backgroundCanvas.saveState();
backgroundCanvas.restoreState();
// Draw border
PdfContentByte lineCanvas = canvases[PdfPTable.LINECANVAS];
float lineWidth = Constants.BORDER_WIDTH_THIN;
lineCanvas.setLineWidth(lineWidth);
lineCanvas.moveTo(llx, ury - lineWidth);
lineCanvas.lineTo(llx, lly);
lineCanvas.lineTo(urx, lly);
lineCanvas.lineTo(urx, ury - lineWidth);
lineCanvas.setColorStroke(BaseColor.BLACK);
lineCanvas.stroke();
lineCanvas.saveState();
lineCanvas.restoreState();
}
This is the order of the different direct content layers:
PdfPtable.BASECANVAS—Anything placed here will be under the table.
PdfPtable.BACKGROUNDCANVAS—This is the layer where the backgrounds are
drawn.
PdfPtable.LINECANVAS—This is the layer where the lines are drawn.
PdfPtable.TEXTCANVAS—This is the layer where the text goes. Anything placed
here will cover the table.
This was taken from the book "iText in Action - Second Edition."
You also ask about saveState() and restoreState(). This is explained in Chapter 2 of the iText 7 tutorial:
First we save the current graphics state with the saveState() method, then we change the state and draw whatever lines or shapes we want to draw, finally, we use the restoreState() method to return to the original graphics state. All the changes that we applied after saveState() will be undone. This is especially interesting if you change multiple values (line width, color,...) or when it's difficult to calculate the reverse change (returning to the original coordinate system).
Your code was too long for me to inspect, but I highly doubt that saveState()/restoreState() would be the cause of your problem.
I would try to avoid nesting tables as much as possible. It is usually much easier (and more efficient) to use colspan and rowspan.
If this doesn't solve your problem, please explain your problem in one sentence.

Rectangle Overlapping in IText Pdf Generating

i tried to create rectangles as in the image, when i tried to create rectangles using coordinates the
two rectangles are placing one after other.
Here is the code how iam creating Rectangle.
when i give coordinates for two rectangles those are generating one after other, i want them to overlap as in the image..How can i make it?
PdfWriter writer= PdfWriter.getInstance(document, new FileOutputStream(filename));
document.open();
PdfContentByte cb = writer.getDirectContent();
Rectangle rect,rect1;
rect = new Rectangle(p1,p2,p3,p4); // CO-ORDINATES OF RECTANGLE
rect.setBorder(Rectangle.BOX);
cb.rectangle(rect);
Please take a look at the Rectangles example to find out how to create a PDF that looks like rectangles.pdf:
When creating a rectangle, you need the coordinates of the lower-left corner and the upper-right corner of the rectangle. For instance:
float llx = 36;
float lly = 700;
float urx = 200;
float ury = 806;
You already know that you need a PdfContentByte instance to draw the first rectangle:
PdfContentByte canvas = writer.getDirectContent();
Rectangle rect1 = new Rectangle(llx, lly, urx, ury);
rect1.setBackgroundColor(BaseColor.LIGHT_GRAY);
rect1.setBorder(Rectangle.BOX);
rect1.setBorderWidth(1);
canvas.rectangle(rect1);
For clarity, I have defined a background color and I've set the border width to 1 pt.
Now when you want to add an extra rectangle that overlaps the same way as described in your question, you need to change the llx and ury value. That's elementary math. For instance:
Rectangle rect2 = new Rectangle(llx + 60, lly, urx, ury - 40);
rect2.setBackgroundColor(BaseColor.DARK_GRAY);
rect2.setBorder(Rectangle.BOX);
rect2.setBorderColor(BaseColor.WHITE);
rect2.setBorderWidth(0.5f);
canvas.rectangle(rect2);
To make sure you see the difference, I've now used another background color, and I defined 0.5 pt as the border width and white as the border color.
It doesn't get any simpler than this.

Categories