How to fade out the edges of a Java2D Area object? - java

I have a method that creates shadows based on where things are on the screen and the final product of that method is an Area, which I then draw onto the screen and it contains all shadows on screen in the same Area. This is because the drawn shadows are a low opacity so if it is not all one thing they will overlap and the opacity will make it look weird.
The issue that I want the shadows to look like they fade out, but I have absolutely no idea how, or if that would even be possible. Is there a way to soften the edges of the Area or make them gradient fade out? I tried to do graphics2D.setPaint(new GradientPaint[a gradient effect]) but it did nothing.
Thanks in advance
EDIT: Here is a screenshot of the program making a shadow around a 'building' rectangle. The green rectangle is to show the effect of the shadow. The end result I want is instead of the shadow abruptly ending, it should fade out.

i guess you have problems setting up the proper colors for the gradient:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(30, 30, 50, 20);
Graphics2D g2d = (Graphics2D) g;
Polygon p = new Polygon(new int[]{10,20,50,50,40,10}, new int[]{10,10,40,50,50,20}, 6);
Area a = new Area(p);
Color b = new Color(0xFF000000, true); //using colors with transparency!!!
Color w = new Color(0x00FFFFFF, true); //using colors with transparency!!!
//try to use proper values for the x/y values - both, start and end
GradientPaint gradient = new GradientPaint(0, 0, b, 40, 40, w);
g2d.setPaint(gradient);
g2d.fill(a); // fill your area!
}
result is a gradient with alpha (Transparency) ...
Area drawn in red <==> Area drawn with Gradient
don't forget to set the alpha value 0xAARRGGBB to FF for 100% opaque (0xFF000000 is black 100%opaque) to 0 (0x00FFFFFF white, 100% transparent)

Related

Can a RadialGradientPaint have multiple center points?

I am currently trying to add a day/night cycle to my game, and I am having trouble with lighting. If there is a light source, I want it to create a circle of light around its area. I've made night time by drawing a black rectangle over the screen that becomes less transparent when its night time. That makes the whole screen darker. The code that I have works for one light source, but if I have a light source that overlaps another light source, it makes a strange darker ring. I understand what is causing that ring, but nothing I have tried is removing the ring.
public void render(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(new Color(20, 20, 20, transparency));
float[] dist = { 0.5f, 1.0f };
Color[] color = { new Color(230, 230, 180, 150), new Color(20, 20, 20, transparency) };
//Color[] color = { new Color(230, 230, 180, 150), new Color(0, 0, 0, 0) }; //Old unused color for the gradient
Area a = new Area(new Rectangle(0, 0, Main_Game.WIDTH, Main_Game.HEIGHT));
for (int i = 0; i < lightList.size(); i++) {
RadialGradientPaint p = new RadialGradientPaint(lightList.get(i).center, lightList.get(i).size, dist, color);
g2d.setPaint(p);
g2d.fillOval(lightList.get(i).center.x - lightList.get(i).size, lightList.get(i).center.y - lightList.get(i).size, lightList.get(i).size*2, lightList.get(i).size*2);
a.subtract(new Area(new Ellipse2D.Double(lightList.get(i).center.x - lightList.get(i).size, lightList.get(i).center.y - lightList.get(i).size, lightList.get(i).size*2, lightList.get(i).size*2)));
}
g2d.setColor(new Color(20, 20, 20, transparency));
g2d.fill(a);
}
That code has a linked list that holds all the light sources. And the transparency variable stores the transparency the box that darkens the screen when it's night time.
The ideal way to fix this would be to combine all the RadialGradientPaint objects for each light into one paint object and that way, the lights wouldn't overlap weirdly.
Here is what it looks like when it works with only one light source:
Here is a picture of the weirdness of lighting that I'm getting at night time (there is no issue during the daytime of the game) when there are two light sources close to each other:
Any sort of help or recommendation to setup this lighting would be greatly appreciated :)
I have tried subtracting one lighting circle from another so that they aren't overlapping, but that made the two lights not merge very nicely. I tried drawing the lights as a rectangle instead of an oval but that made a similar issue.

Can I assign an image gradient to a rectangle

I'm currently making Mario as a school graphics project.
I have most of the collisions done, but I just want the land and bricks to actually look like land and bricks instead of just colored rectangles. I have an ImageIcon for the "land" in my graphics project. The problem is that it is only 16x16 pixels large. In order to make enough land by just making each part of the land one 16x16 pixel, it would essentially be horribly inefficient.
I was wondering if I could get the ImageIcon or possibly buffered image and use it as the "color" for a rectangle to make the chunks of land easier. If that's not possible, can you offer other suggestions on how to go about this problem?
Using a background of tiled images, a texture:
private BufferedImage image;
URL url = getClass().getResource("/mytexture.png");
assert url != null;
image = ImageIO.read(url);
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Rectangle rect = new Rectangle(0, 0, width, height);
Rectangle textureRect = new Rectangle(0, 0, image.getWidth(), image.getHeight());
TexturePaint paint = new TexturePaint(image, textureRect);
g2.setPaint(paint);
g2.fill(rect);
}
In general create your own JPanel.
Gimp and other tools allow to create a tilable image by ensuring that a line running to a border will enter at the opposite border.

Graphics2D is incapable of alpha?

I am implementing layers in a 2D engine of mine and I would like layers to be stackable, and I would also like a feature to 'cut holes' in layers - this means using alpha. However when writing it standalone I can't seem to get anything to use true alpha, it tried to fake it by blending colours together, for instance (my code):
BufferedImage background, foreground;
public GraphicsTest() {
background = new BufferedImage(500,500,BufferedImage.TYPE_INT_ARGB);
foreground = new BufferedImage(500,500,BufferedImage.TYPE_INT_ARGB); // Fully capable of alpha.
Random r = new Random();
int white = Color.white.getRGB();
// Draw random white dots
for (int i=0; i<500; i++) {
int
x = r.nextInt(500),
y = r.nextInt(500);
background.setRGB(x, y, white);
}
}
#Override
protected void paintComponent(Graphics g) {
BufferedImage output = new BufferedImage(500,500,BufferedImage.TYPE_INT_ARGB); // Fully capable of alpha.
Graphics2D canvas = output.createGraphics();
Graphics2D overlay = foreground.createGraphics();
canvas.drawImage(background, 0, 0, null);
overlay.setColor(Color.white);
overlay.fillRect(0, 0, 500, 500);
// Start drawing with alpha
overlay.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
overlay.setColor(new Color(0xFF000000)); // Overwrite/draw full alpha, colour doesn't matter when alpha is 100%
overlay.fillRect(100, 100, 125, 87);
//canvas.setColor(Color.red);
//canvas.fillRect(0, 0, 500, 500);
canvas.drawImage(foreground, 0, 0, null);
overlay.dispose();
canvas.dispose();
g.drawImage(output, 0, 0, null);
// Also write to a file for manual raw pixel checking
try {
// Does output a 32-bit depth image
ImageIO.write(output, "PNG", new File("c:/output.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
This is the most basic test I could think of, I have two images, one for the background and one for the foreground, I am needing to see the background through the foreground in the 127x87 (random sized) box.
Currently it results in a white screen with a black box, the back box should be that part of the background.
I have tried all sorts of methods here, settings the composite and drawing black with full alpha but when combined I never see the background, the setColor(0xFF000000) doesn't seem to be doing anything but drawing black and the composite is the culprit of this 'faking', instead of overwriting FF000000 (black) on a 00FFFFFF (white with no alpha) background yielding FF000000 (what I set it to) it is instead 007F7F7F (grey with no alpha).
The only reason I can see for this is the fact that everything is going through a Graphics2D object, I cannot use output.setRGB as it is slow and I wouldn't know how to draw custom shapes with it.
You need to change 3 things for your code to behave properly.
As #vandale said, you need to use a color with actual alpha with the boolean constructor.
Second, the alpha is the opacity (so it should be 0 for transparent color). You get:
overlay.setColor(new Color(0x0000000, true));
Third, you need to say that your paint operation will actually override whatever was already there (you don't want to draw on top (ATOP) of the existing but rather replace with the transparent color (IN) what was there). You get:
overlay.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 1f));

Why doesn't BufferedImage of type TYPE_INT_ARGB support the AlphaComposite.CLEAR rule? [duplicate]

I have an off-screen BufferedImage, constructed with the type BufferedImage.TYPE_INT_ARGB. It can contain anything, and I'm looking for a way to (fairly efficiently) completely overwrite the image with transparent pixels, resulting in an 'invisible' image.
Using something like this:
(bufimg.getGraphics()).setColor(new Color(10, 10, 100, 0));
(bufimg.getGraphics()).fillRect (0, 0, x, y);
Has no effect. One possible method might be just to write over every pixel in the BufferedImage, but I'm not sure this is the best solution. How would you do it?
[edit]
The Graphics documentation advises against using clearRect for off-screen images, but I have tried it with the same results as above.
[edit2]
After experimenting with MeBigFatGuy's code (thanks!), it does clear an image. But it also stops further painting to that image (or appears to). This code for example:
BufferedImage img = new BufferedImage (600, 600, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.createGraphics ()
g.drawLine (100, 100, 500, 500);
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
g.setComposite(composite);
g.setColor(new Color(0, 0, 0, 0));
g.fillRect(0, 0, 600, 600);
graphicsAI.setColor(new Color (10, 10, 10, 255));
graphicsAI.drawLine (100, 100, 500, 500);
Results in nothing seen on the image (I'm drawing the image to a JPanel). Is this something to do with the addition of alpha values?
After you clear the background with the CLEAR composite, you need to set it back to SRC_OVER to draw normally again. ex:
//clear
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
g2.fillRect(0,0,256,256);
//reset composite
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
//draw
g2.setPaint(Color.RED);
g2.fillOval(50,50,100,100);
You could get the underlying int[] array of your BufferedImage (make sure to use a compatible format: that is, one that is backed by an int[]).
Then fill the int[] with ints whose alpha value are 0 (0 will do ; )
A System.arraycopy will be very fast.
You have to know that directly writing in the int[] is a lot faster than using setRGB.
Now BufferedImage are a bit of a black art in Java: depending on what you're doing and on which platform/JVM you're doing it, you may lose hardware acceleration (which may never have been there in the first place anyway). In addition to that, you may very well not care at all about hardware acceleration anyway because you may not be working on, say, a game requiring 60+ FPS to be playable etc.
This is a very complicated topic and there's more than one way to skin the BufferedImage cat. As far as I'm concerned I work directly in the int[] when I've got to mess at the pixel level because I think it makes much more sense than trying to use higher-level drawing primitives and I do really don't care about the potential lost of hardware acceleration.
If you cast the Graphics object to a Graphics2D object, you can set a Composite object thru
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
Graphics2D g2d = (Graphics2D) image.getGraphics();
g2d.setComposite(composite);
g2d.setColor(new Color(0, 0, 0, 0));
g2d.fillRect(0, 0, 10, 10);
For the sake of completeness, here is a working, testing, and fast function that is cross-platform compliant.
static public BufferedImage createTransparentBufferedImage(int width, int height) {
// BufferedImage is actually already transparent on my system, but that isn't
// guaranteed across platforms.
BufferedImage bufferedImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = bufferedImage.createGraphics();
// To be sure, we use clearRect, which will (unlike fillRect) totally replace
// the current pixels with the desired color, even if it's fully transparent.
graphics.setBackground(new Color(0, true));
graphics.clearRect(0, 0, width, height);
graphics.dispose();
return bufferedImage;
}
Despite you saying it doesn't work, I used clearRect quite fine.
Clears the specified rectangle by filling it with the background color
of the current drawing surface. This operation does not use the
current paint mode.
Beginning with Java 1.1, the background color of offscreen images may
be system dependent. Applications should use setColor followed by
fillRect to ensure that an offscreen image is cleared to a specific
color.
Fills the specified rectangle. The left and right edges of the
rectangle are at x and x + width - 1. The top and bottom edges are at
y and y + height - 1. The resulting rectangle covers an area width
pixels wide by height pixels tall. The rectangle is filled using the
graphics context's current color.
It is not clearly stated here that one will set the rectangle to the background color, while the other will paint with the foreground color on top of the current colors, but it's what it seems to do.
This is pure speculation, but I think the note about offscreen images relates to Graphics objects obtained from offscreen AWT components, as they are native. I can hardly imagine how the background color of a BufferedImage could be system dependent. As the API doc is for Graphics, this could be a generalization not applying to the BufferedImage case.
My testing code:
JFrame jf = new JFrame();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BufferedImage img = new BufferedImage(200, 300, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
//fill right half with opaque white
g.setColor(Color.WHITE);
g.fillRect(100, 0, 100, 300);
//leave top third as it is
//fill middle third with transparent color
g.setColor(new Color(0, true));
g.fillRect(0, 100, 200, 100);
//clear bottom third with transparent color
g.setBackground(new Color(0, true));
g.clearRect(0, 200, 200, 100);
g.dispose();
jf.add(new JLabel(new ImageIcon(img)));
jf.pack();
jf.setVisible(true);
the result is two white squares, top right. Where no white was painted, or clearRect was used to overwrite the white, the result is a light gray, the frame's default background color.
Performance-wise, it's regular drawing. arraycopy might well be faster, I don't know, but at least this is likely hardware accelerated just as any other drawing operation.
A plus point versus the array solution is a) no additional memory and b) independence from the color model; this should work no matter how the image was set up.
A minus point versus the Composite solution is that it only allows clearing rectangles; setting the composite allows you to clear any kind of shape.
Setting the background of the graphics Object seems to do the job:
g.setBackground(new Color(0, 0, 0, 0));
(at least when drawing images for scaling purposes)

How to draw a shaded area between two lines

How do you draw a shaded area between two lines of a certain color?
I'm using Graphics2D.drawLine() to draw the lines and to have a translucent shaded area of color between the lines.
This should possible with GradientPaint
Somthing like that:
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
Polygon p = new Polygon();
p.addPoint(0,100);
p.addPoint(100,100);
p.addPoint(100,200);
p.addPoint(100,200);
GradientPaint gp = new GradientPaint(0.0f, 100.0f, Color.red,
200.0f, 200.0f, Color.green, true);
g2.setPaint(gp);
g2.fill(p);
}
For transparency you need to include settings for the alpha channel.
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
alpha));
For working examples see this article
You are thinking the wrong way around. If you want to draw an area, do so. Render the lines on top of the area afterwards.
Areas can be rendered with Graphics.drawPolygon.
There are two ways to get translucency. The simplest way (for solid color) is to create the Color instance with alpha (new Color(0xAARRGGBB, true) and use that for drawing.
The other way is to use Graphics2D.setComposite with an instance of AlphaComposite (that method also affects the drawing of element that do not make use of the color, e.g. drawImage).

Categories