Graphics2D: How to create consistent padding around an irregular shape? - java

I'm using Java Graphics2D to generate this map with some sort of tinted red overlay over it. As you can see, the overlay gets cut off along the image boundary on the left side:-
After demo'ing this to my project stakeholders, what they want is for this overlay to clip along the map boundary with some consistent padding around it. The simple reason for this is to give users the idea that the overlay extends outside the map.
So, my initial thought was to perform a "zoom and shift", by creating another larger map that serves as a "cookie cutter", here's my simplified code:-
// polygon of the map
Polygon minnesotaPolygon = ...;
// convert polygon to area
Area minnesotaArea = new Area();
minnesotaArea.add(new Area(minnesotaPolygon));
// this represents the whole image
Area wholeImageArea = new Area(new Rectangle(mapWidth, mapHeight));
// zoom in by 8%
double zoom = 1.08;
// performing "zoom and shift"
Rectangle bound = minnesotaArea.getBounds();
AffineTransform affineTransform = new AffineTransform(g.getTransform());
affineTransform.translate(-((bound.getWidth() * zoom) - bound.getWidth()) / 2,
-((bound.getHeight() * zoom) - bound.getHeight()) / 2);
affineTransform.scale(zoom, zoom);
minnesotaArea.transform(affineTransform);
// using it as a cookie cutter
wholeImageArea.subtract(minnesotaArea);
g.setColor(Color.GREEN);
g.fill(wholeImageArea);
The reason I'm filling the outside part with green is to allow me to see if the cookie cutter is implemented properly. Here's the result:-
As you can see, "zoom and shift" doesn't work in this case. There is absolutely no padding at the bottom right. Then, I realized that this technique will not work for irregular shape, like the map... and it only works on simpler shapes like square, circle, etc.
What I want is to create consistent padding/margin around the map before clipping the rest off. To make sure you understand what I'm saying here, I photoshopped this image below (albeit, poorly done) to explain what I'm trying to accomplish here:-
I'm not sure how to proceed from here, and I hope you guys can give me some guidance on this.
Thanks.

I'll just explain the logic, as I don't have time to write the code myself. The short answer is that you should step through each pixel of the map image and if any pixels in the surrounding area (i.e. a certain distance away) are considered "land" then you register the current pixel as part of the padding area.
For the long answer, here are 9 steps to achieve your goal.
1. Decide on the size of the padding. Let's say 6 pixels.
2. Create an image of the map in monochrome (black is "water", white is "land"). Leave a margin of at least 6 pixels around the edge. This is the input image: (it isn't to scale)
3. Create an image of a circle which is 11 pixels in diameter (11 = 6*2-1). Again, black is empty/transparent, white is solid. This is the hit-area image:
4. Create a third picture which is all black (to start with). Make it the same size as the input image. It will be used as the output image.
5. Iterate each pixel of the input image.
6. At that pixel overlay the hit-area image (only do this virtually, via calculation), so that the center of the hit-area (the white circle) is over the current input image pixel.
7. Now iterate each pixel of the hit-area image.
8. If the any white pixel of the hit-area image intersects a white pixel of the input image then draw a white pixel (where the center of the circle is) into the output image.
9. Go to step 5.
Admittedly, from step 6 onward it isn't so simple, but it should be fairly easy to implement. Hopefully you understand the logic. If my explanation is too confusing (sorry) then I could spend some time and write the full solution (in Javascript, C# or Haskell).

Related

Slick2d using anti-aliasing with Animations/Images

I'm using slick2d to render an Animation, but using anti-aliasing
Without AA, the movement is choppy, as one would expect for something without AA.
I turn on AA in my game's preRenderState with:
g.setAntiAlias(true);
This leads to:
Note the diagonal line in the center, presumably caused by the two triangles that render the rectangle not meeting precicely. How can I remove that while still using AA to smooth my movement? I found http://www.java-gaming.org/index.php?topic=27313.0 but the solution was "remove AA" which I am reluctant to do.
This looks suspiciously like artifacts due to GL_POLYGON_SMOOTH.
When you use that (deprecated) functionality, you are expected to draw all opaque geometry with blending enabled and the blend function: GL_SRC_ALPHA_SATURATE, GL_ONE. Failure to do so produces white outlines on most contours (aliased edges, basically).
Chapter 6 of the OpenGL Redbook states:
Now you need to blend overlapping edges appropriately. First, turn off the depth buffer so that you have control over how overlapping pixels are drawn. Then set the blending factors to GL_SRC_ALPHA_SATURATE (source) and GL_ONE (destination). With this specialized blending function, the final color is the sum of the destination color and the scaled source color; the scale factor is the smaller of either the incoming source alpha value or one minus the destination alpha value.
This means that for a pixel with a large alpha value, successive incoming pixels have little effect on the final color because one minus the destination alpha is almost zero. With this method, a pixel on the edge of a polygon might be blended eventually with the colors from another polygon that's drawn later. Finally, you need to sort all the polygons in your scene so that they're ordered from front to back before drawing them.
That is a lot of work that almost nobody does correctly when they try to enable GL_POLYGON_SMOOTH.
Every time you enable anti-aliasing in Slick2D, it should look like this.
g.setAntiAlias(true);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA_SATURATE, GL11.GL_ONE);
Basically same as Andon's answer, but with code that should solve the problem.
Note that with this whatever you draw first will be on top, rather than what you draw last. So you may need to reverse rendering order.

Rotating a Sprite in Java

While working on Projectiles I thought that it would be a good idea to rotate the sprite as well, to make it look nicer.
I am currently using a 1-Dimensional Array, and the sprite's width and height can and will vary, so it makes it a bit more difficult for me to figure out on how to do this correctly.
I will be honest and straight out say it: I have absolutely no idea on how to do this. There have been a few searches that I have done to try to find some stuff, and there were some things out there, but the best I found was this:
DreamInCode ~ Rotating a 1-dimensional Array of Pixels
This method works fine, but only for square Sprites. I would also like to apply this for non-square (rectangular) Sprites. How could I set it up so that rectangular sprites can be rotated?
Currently, I'm attempting to make a laser, and it would look much better if it didn't only go along a vertical or horizontal axis.
You need to recalculate the coordinate points of your image (take a look here). You've to do a matrix product of every point of your sprite (x, y) for the rotation matrix, to get the new point in the space x' and y'.
You can assume that the bottom left (or the bottom up, depends on your system coordinate orientation) of your sprite is at (x,y) = (0,0)
And you should recalculate the color too (because if you have a pure red pixel surrounded by blue pixel at (x,y)=(10,5) when you rotate it can move for example to (x, y)=(8.33, 7.1) that it's not a real pixel position because pixel haven't float coordinate. So the pixel at real position (x, y)=(8, 7) will be not anymore pure red, but a red with a small percentage of blue)... but one thing for time.
It's easier than you think: you only have to copy the original rectangular sprites centered into bigger square ones with transparent background. .png files have that option and I think you may use them.

Java drawImage interpolation

My question involves the drawImage method in Java Graphics2D (this is for a desktop app, not Android).
My BufferedImage that I'd like to draw contains high resolution binary data, most pixels are black but I have some sparse green pixels (the green pixels represent data points from an incoming raw data stream). The bitmap is quite large, larger than my typical panel size. I made it large so I could zoom in and out. The problem is when I zoom out I lose some of my green pixels .. as an example if my image is 1000 pixels and by panel is 250 pixels, I'd lose 1 out of 4 pixels in each direction (X and Y). If I use nearest neighbor interpolation when I scale the pixels can just disappear to black. If I use something like bilinear interpolation my green pixel will get recolored to somewhere between black and green.
I understand all this behavior, but my question is that is there any way to get the behavior I want, which is to make sure if any pixels is non-black I want it to be drawn at it's full intensity. Perhaps something like a "max-hold" interpolation.
I realize I could probably do what I want by drawing shape primitive over a black background, and maybe this is what I'll have to do. But there is a reason I'm using bitmaps (has to do with the fact that I'm showing the data in a falling spectrogram-type display - and it does have a mode where all the pixels could be colored and not just black and green).
Thanks,
You could look at the implementation of drawImage and override it to get your desired behaviour, however probably the core of the scaling uses hardware acceleration, so re implementing it in Java would be really slow.
You could look into JOGL, but my impression is that, if your pixels are really sparse, just drawing the green pixels on a black background (or over an image) would be both easy to code and very fast.
You could even have an heuristic switching between painting the dots to scaling the image if the number of dots starts being too high.

how to increase opacity in gaussian blur

I have a Java application where I need to draw text on top of an image. The text, the font, and the image are all determined at run time. The text needs to look nice, yet be readable (sufficiently contrastive) on top of the image.
To meet these requirements, I create a drop shadow. This is done by drawing the text in opaque black, on a blank/transparent BufferedImage, then applying a Gaussian blur filter. I then draw the text again, in opaque white, on top of the drop shadow. So I have opaque white text, with a black blurred shadow around it that quickly fades to full transparency. I can then draw this image on top of the background image.
The problem I'm trying to solve is that the drop shadow seems too transparent. So against bright, busy backgrounds, it doesn't give the white text enough separation.
So how to increase the opacity of the shadow? I've tried increasing the radius of the gaussian blur, and that makes the shadow wider, but doesn't make it more opaque.
The code I'm using is based on this DropShadowDemo by Romain Guy. I use his createDropShadow() and gaussianBlurFilter(). But instead of painting the drop shadow and the text separately during paintComponent(), I draw them both onto a BufferedImage in advance; and I draw this single BufferedImage on top of the background during paintComponent(). Maybe that's my problem? But I don't see how that would decrease the opacity of the shadow. I'm not using g2.setComposite() during paintComponent().
I've looked at adjusting the opacity of the drop shadow using some kind of BufferedImageOp, such as a LookupOp. But it seems like a lot of work for a simple adjustment (creating four arrays of numbers, I guess). I don't think a RescaleOp would work, since I want the result alpha to fall in the same range (0 to 1) as the source alpha. If I could specify a BufferedImageOp that sets new alpha = sqrt(old alpha), or something like that, that would be ideal. But I don't know an easy way to do that.
Details of the code can be seen here:
ShadowedText.java (creates the text-with-drop-shadow image)
SetBeforeMe.java (implements paintComponent() that draws the image)
I would include relevant code blocks here, but it seems like the relevant amount of code is too big (wall of code)... might as well just give links to the source files.
Update
It looks like Change the alpha value of a BufferedImage? would be a way to change the opacity of the drop shadow... basically recalculating the alpha value of each pixel, one by one. TBD: whether it's portable (to 64-bit machines, e.g.), and whether it's fast enough. If I do a = sqrt(a) or a = sin(a * pi * 0.5) on every pixel (thinking of a in the range 0 to 1), will that be slow? I would be happy to know if there's a simpler way that takes advantage of available optimizations, like the built-in BufferedImageOps presumably do. Maybe the answer is building arrays for LookupOp after all. Anybody know of some example code for that?
Final update
Solved using a LookupOp; see code below.
Below is the code I ended up with to make a BufferedImage more opaque. I decided to go ahead and use a LookupOp, rather than a potentially unportable and slow loop over getRGB / setRGB on each pixel. The work to set up Lookup arrays wasn't so bad.
/* Make alpha channel more opaque.
* Modify the alpha (opacity) channel so that values are higher, but still
* continuous and monotonically increasing.
*/
private static void adjustOpacity(BufferedImage shadowImage) {
// Use a lookup table with four arrays;
// the three for RGB are identity arrays (no change).
byte identityArray[] = new byte[256];
for (int i=0; i < 256; i++)
identityArray[i] = (byte)i;
byte alphaArray[] = new byte[256];
// map the range (0..256] to (0..pi/2]
double mapTo90Deg = Math.PI / 2.0 / 256.0;
for (int i=0; i < 256; i++) {
alphaArray[i] = (byte)(Math.sin(i * mapTo90Deg) * 256);
}
byte[][] tables = {
identityArray,
identityArray,
identityArray,
alphaArray
};
ByteLookupTable blut = new ByteLookupTable(0, tables);
LookupOp op = new LookupOp(blut, null);
// With LookupOp, it's ok for src and dest to be the same image.
op.filter(shadowImage, shadowImage);
}
It seems to work (though I haven't taken before-and-after screenshots for comparison).

Rotating an image back to it's original state

I have a program that needs to take in a photo taken by an iphone (or any kind of decent camera) of a 7x10 grid with a thick black boarder around the edges. This image can be received rotated to the right or to the left (there's no need to worry about skew). I have an image of the grid in its original state already, but I need to get the picture that I'm taking in and rotate it to its "perfect/original" state.
Idea 1: Performance Hog/Bad Results
Threshold the picture that I receive and the perfect grid Image I already have. Compare each pixel for 0 rotation, get a total score, and save it. Do do this rotating the image of increments by 1 to 359. The lowest score is the rotation we need to get the picture back to its original state.
Idea 2: Still Unsure How To Go About Doing This
Threshold the picture that I receive and the perfect grid Image I already have. Draw a a line through the center of the picture vertically and horizontally. Find the rotation based on the black pixel count that the vertical and horizontal line passed through. This would require some sort of Trigonometry that I'm not to great with understanding.
Does anyone have any other ideas for getting this working?
Any help for pointing me in the right direction would be greatly appreciated!
Thanks!
Instead of drawing one horizontal and one vertical line, draw instead two horizontal lines (say, each at a third of the picture). Only look at the left halves of these lines and calculate how many black pixels there are on the path of each (a1 and a2). You also have to keep track of the distance between the two red lines, so the number of pixels d.
Using this notation in the figure above, your desired angle is:
alpha=atan2((a2-a1),d)
and a counterclockwise rotation by alpha will bring the white portion of the picture into proper alignment.

Categories