Can someone please explain iTexts canvas drawing order? - java

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.

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

Creating a 3D shadow effect on images with itextpdf

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);

Render Fog-Of-War on 2d tilemap

I am currently creating a small 2d-game with lwjgl.
I tried to figure out a way of implementing a Fog-Of-War.
I used a black backgound with alpha set to 0.5.
Then I added a Square, to set alpha to 1 for each tile, which is lit, ending up having a black Background with differend Alpha values.
Then I rendered my Background using the blendfunction:
glBlendFunc(GL_ZERO, GL_SRC_ALPHA)
This works well, but now I have a problem with adding a second layer with transparent parts and apply the Fog-Of-War on them, too.
I've read something about FrameBufferObjects, but I don't know how to use them and if they are the right choice.
Later on I want to lit tiles with an texture/Image to give it a smoother look. So these textures may overlap. This is the reason why I chose to first render the Fog-Of-War.
Do you have an idea how to fix this problem?
Thanks to samgak.
Now I try to render a dark square on each dark tile exept the lit tiles.
I divided each tile in an 8x8 grid for more details. This is my method:
public static void drawFog() {
int width = map.getTileWidth()>>3; //Divide by 8
int height = map.getTileHeight()>>3;
int mapWidth = map.getWidth() << 3;
int mapHeight = map.getHeight() << 3;
//background_x/y is the position of the background in pixel
int mapStartX = (int) Math.floor(background_x / width);
int mapStartY = (int) Math.floor(background_y / height);
//Multiply each color component with 0.5 to get a darker look
glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
glBegin(GL_QUADS);
//RENDERED_TILES_X/Y is the amount of tiles to fill the screen
for(int x = mapStartX; x < (RENDERED_TILES_X<<3) + mapStartX
&& x < mapWidth; x++){
for(int y = mapStartY; y < (RENDERED_TILES_Y<<3) + mapStartY
&& y < mapHeight; y++){
//visible is an boolean-array for each subtile
if(!visible[x][y]){
float tx = (x * width) - background_x;
float ty = (y * height) - background_y;
glVertex2f(tx, ty);
glVertex2f(tx+width, ty);
glVertex2f(tx+width, ty+height);
glVertex2f(tx, ty+height);
}
}
}
glEnd();
}
I set the visible array to false except for an small square.
It will render fine, but if I move the background the whole screen except the visible square turns black.
One approach is to render the Fog-of-War layer last, using an untextured black square rendered over the top of all the other layers after they have been rendered.
Use this blend function:
glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA)
and set the Fog-of-War alpha per-vertex so that when it is 1.0 the black overlay is transparent, and when it is 0.0, it is entirely black. (If you want the alpha to have the opposite meaning, just swap the arguments).
To make it more smooth you can set the alpha per vertex at each of the corners of the square to vary smoothly across it. You could also use a texture with varying alpha values instead of a plain black square, or subdivide the square into 4 or 16 squares to allow finer control.

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.

Efficient 2D Tile based lighting system

What is the most efficient way to do lighting for a tile based engine in Java?
Would it be putting a black background behind the tiles and changing the tiles' alpha?
Or putting a black foreground and changing alpha of that? Or anything else?
This is an example of the kind of lighting I want:
There are many ways to achieve this. Take some time before making your final decision. I will briefly sum up some techiques you could choose to use and provide some code in the end.
Hard Lighting
If you want to create a hard-edge lighting effect (like your example image),
some approaches come to my mind:
Quick and dirty (as you suggested)
Use a black background
Set the tiles' alpha values according to their darkness value
A problem is, that you can neither make a tile brighter than it was before (highlights) nor change the color of the light. Both of these are aspects which usually make lighting in games look good.
A second set of tiles
Use a second set of (black/colored) tiles
Lay these over the main tiles
Set the new tiles' alpha value depending on how strong the new color should be there.
This approach has the same effect as the first one with the advantage, that you now may color the overlay tile in another color than black, which allows for both colored lights and doing highlights.
Example:
Even though it is easy, a problem is, that this is indeed a very inefficent way. (Two rendered tiles per tile, constant recoloring, many render operations etc.)
More Efficient Approaches (Hard and/or Soft Lighting)
When looking at your example, I imagine the light always comes from a specific source tile (character, torch, etc.)
For every type of light (big torch, small torch, character lighting) you
create an image that represents the specific lighting behaviour relative to the source tile (light mask). Maybe something like this for a torch (white being alpha):
For every tile which is a light source, you render this image at the position of the source as an overlay.
To add a bit of light color, you can use e.g. 10% opaque orange instead of full alpha.
Results
Adding soft light
Soft light is no big deal now, just use more detail in light mask compared to the tiles. By using only 15% alpha in the usually black region you can add a low sight effect when a tile is not lit:
You may even easily achieve more complex lighting forms (cones etc.) just by changing the mask image.
Multiple light sources
When combining multiple light sources, this approach leads to a problem:
Drawing two masks, which intersect each other, might cancel themselves out:
What we want to have is that they add their lights instead of subtracting them.
Avoiding the problem:
Invert all light masks (with alpha being dark areas, opaque being light ones)
Render all these light masks into a temporary image which has the same dimensions as the viewport
Invert and render the new image (as if it was the only light mask) over the whole scenery.
This would result in something similar to this:
Code for the mask invert method
Assuming you render all the tiles in a BufferedImage first,
I'll provide some guidance code which resembles the last shown method (only grayscale support).
Multiple light masks for e.g. a torch and a player can be combined like this:
public BufferedImage combineMasks(BufferedImage[] images)
{
// create the new image, canvas size is the max. of all image sizes
int w, h;
for (BufferedImage img : images)
{
w = img.getWidth() > w ? img.getWidth() : w;
h = img.getHeight() > h ? img.getHeight() : h;
}
BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
// paint all images, preserving the alpha channels
Graphics g = combined.getGraphics();
for (BufferedImage img : images)
g.drawImage(img, 0, 0, null);
return combined;
}
The final mask is created and applied with this method:
public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask)
{
int width = image.getWidth();
int height = image.getHeight();
int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);
for (int i = 0; i < imagePixels.length; i++)
{
int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha
// get alpha from color int
// be careful, an alpha mask works the other way round, so we have to subtract this from 255
int alpha = (maskPixels[i] >> 24) & 0xff;
imagePixels[i] = color | alpha;
}
image.setRGB(0, 0, width, height, imagePixels, 0, width);
}
As noted, this is a primitive example. Implementing color blending might be a bit more work.
Raytracing might be the simpliest approach.
you can store which tiles have been seen (used for automapping, used for 'remember your map while being blinded', maybe for the minimap etc.)
you show only what you see - maybe a monster of a wall or a hill is blocking your view, then raytracing stops at that point
distant 'glowing objects' or other light sources (torches lava) can be seen, even if your own light source doesn't reach very far.
the length of your ray gives will be used to check amount light (fading light)
maybe you have a special sensor (ESP, gold/food detection) which would be used to find objects that are not in your view? raytrace might help as well ^^
how is this done easy?
draw a line from your player to every point of the border of your map (using Bresehhams Algorithm http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
walk along that line (from your character to the end) until your view is blocked; at this point stop your search (or maybe do one last final iteration to see what did top you)
for each point on your line set the lighning (maybe 100% for distance 1, 70% for distance 2 and so on) and mark you map tile as visited
maybe you won't walk along the whole map, maybe it's enough if you set your raytrace for a 20x20 view?
NOTE: you really have to walk along the borders of viewport, its NOT required to trace every point.
i'm adding the line algorithm to simplify your work:
public static ArrayList<Point> getLine(Point start, Point target) {
ArrayList<Point> ret = new ArrayList<Point>();
int x0 = start.x;
int y0 = start.y;
int x1 = target.x;
int y1 = target.y;
int sx = 0;
int sy = 0;
int dx = Math.abs(x1-x0);
sx = x0<x1 ? 1 : -1;
int dy = -1*Math.abs(y1-y0);
sy = y0<y1 ? 1 : -1;
int err = dx+dy, e2; /* error value e_xy */
for(;;){ /* loop */
ret.add( new Point(x0,y0) );
if (x0==x1 && y0==y1) break;
e2 = 2*err;
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
}
return ret;
}
i did this whole lightning stuff some time ago, a* pathfindin feel free to ask further questions
Appendum:
maybe i might simply add the small algorithms for raytracing ^^
to get the North & South Border Point just use this snippet:
for (int x = 0; x <map.WIDTH; x++){
Point northBorderPoint = new Point(x,0);
Point southBorderPoint = new Point(x,map.HEIGHT);
rayTrace( getLine(player.getPos(), northBorderPoint), player.getLightRadius()) );
rayTrace( getLine(player.getPos(), southBorderPoint, player.getLightRadius()) );
}
and the raytrace works like this:
private static void rayTrace(ArrayList<Point> line, WorldMap map, int radius) {
//int radius = radius from light source
for (Point p: line){
boolean doContinue = true;
float d = distance(line.get(0), p);
//caclulate light linear 100%...0%
float amountLight = (radius - d) / radius;
if (amountLight < 0 ){
amountLight = 0;
}
map.setLight( p, amountLight );
if ( ! map.isViewBlocked(p) ){ //can be blockeb dy wall, or monster
doContinue = false;
break;
}
}
}
I've been into indie game development for about three years right now. The way I would do this is first of all by using OpenGL so you can get all the benefits of the graphical computing power of the GPU (hopefully you are already doing that). Suppose we start off with all tiles in a VBO, entirely lit. Now, there are several options of achieving what you want. Depending on how complex your lighting system is, you can choose a different approach.
If your light is going to be circular around the player, no matter the fact if obstacles would block the light in real life, you could choose for a lighting algorithm implemented in the vertex shader. In the vertex shader, you could compute the distance of the vertex to the player and apply some function that defines how bright things should be in function of the computed distance. Do not use alpha, but just multiply the color of the texture/tile by the lighting value.
If you want to use a custom lightmap (which is more likely), I would suggest to add an extra vertex attribute that specifies the brightness of the tile. Update the VBO if needed. Same approach goes here: multiply the pixel of the texture by the light value. If you are filling light recursively with the player position as starting point, then you would update the VBO every time the player moves.
If your lightmap depends on where the sunlight hits your level, you could combine two sort of lighting techniques. Create one vertex attribute for the sun brightness and another vertex attribute for the light emitted by light points (like a torch held by the player). Now you can combine those two values in the vertex shader. Suppose the your sun comes up and goes down like the day and night pattern. Let's say the sun brightness is sun, which is a value between 0 and 1. This value can be passed to the vertex shader as a uniform. The vertex attribute that represents the sun brightness is s and the one for light, emitted by light points is l. Then you could compute the total light for that tile like this:
tileBrightness = max(s * sun, l + flicker);
Where flicker (also a vertex shader uniform) is some kind of waving function that represents the little variants in the brightness of your light points.
This approach makes the scene dynamic without having to recreate continuously VBO's. I implemented this approach in a proof-of-concept project. It works great. You can check out what it looks like here: http://www.youtube.com/watch?v=jTcNitp_IIo. Note how the torchlight is flickering at 0:40 in the video. That is done by what I explained here.

Categories