I am trying to render basic stuff on a canvas with its GraphicsContext. I set an opaque color for strokes, but the result on screen is slightly transparent.
public class Spielwiese extends Application {
public static void main(String... args) {
launch(args);
}
#Override
public void start(Stage window) {
Canvas canvas = new Canvas(800, 600);
window.setScene(new Scene(new Pane(canvas)));
window.show();
GraphicsContext g = canvas.getGraphicsContext2D();
g.setFill(Color.BLACK);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
g.setStroke(Color.WHITE);
g.strokeRect(16, 16, 64, 64);
}
}
The result:
I tried setting the transparency of the color myself using new Color(1, 1, 1, 1), but it had the same effect.
However, I managed to get an opaque rectangle by calling the g.strokeRect(16, 16, 64, 64); multiple times to stroke over it more than once, but I don't like that "solution".
Is there a way to stroke an opaque shape onto a canvas without stroking over it multiple times?
EDIT: When I copy the statement g.strokeRect(16, 16, 64, 64); and put 4 of those at the end of the start method instead of one, I get an opaque rectangle:
You should add g.setStrokeWidth(2.0); or similar to your code. If you are drawing very thin lines they appear semi-transparent when they are not exactly aligned with the pixel boundaries.
See for more details: https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/shape/Shape.html
Related
I try to prepare a BufferedImage with semi-transparency so that whereever this image is painted on top of the background will partially shine through. For this purpose I use among other things Graphics.drawLine() with a stroke > 1 and colors that have an alpha component < 255, i.e. are semi-transparent. Lines I draw will often overlap or intersect to make sure there will be no gaps. When such intersections occur, the colors of the lines will stack, i.e. the color that was there before and the semi-transparent color I am painting with will result in a new color. Which is logical.
That is not what I want, however. If possible, I would like to paint with my semi-transparent color as if it was completely opaque and the alpha component of the color just a fourth color component. I would like to copy the color I am painting with - including the alpha component - exactly to the image, replacing everything that was there before.
Is there a way to do this?
The following code produces a simple semi-transparent red X, that will let the background partially shine through wherever it is painted on top of. It will have, however, a darker part where both lines intersect. I would like to be able to paint the X without this darker part. Is that possible (while still using Graphics.drawLine())?
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
public class Test {
public static void main(String[] args) {
// create a new BufferedImage with alpha channel
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
// set a semi-transparent color and a stroke wider than one pixel
g.setColor(new Color(255, 0, 0, 128 /* alpha; semi-transparent */));
g.setStroke(new BasicStroke(2));
// draw a cross; where the lines cross the color will (logically) be darker
g.drawLine(0, 0, 100, 100);
g.drawLine(0, 100, 100, 0);
// use the image somehow, in this case write it to file
writeImage(img, "test.png");
}
private static void writeImage(BufferedImage img, String fileName) {
try {
ImageIO.write(img, "png", new File(fileName));
}
catch (IOException e) {
System.out.println(e);
}
}
}
I could, of course, solve this manually by implementing my own line drawing method and manipulate the pixels directly. (Not sure if that would create new problems with clipping which I also need.) However, that would increase the effort substantially which I would like to avoid.
A further constraint: I am restricted to Java 7.
Create a second image.
Draw on that second image with opaque colors.
Copy that second image onto your original image using translucent alpha.
import java.io.*;
import javax.imageio.*;
public class TranslucentX {
public static void main(String[] args) {
BufferedImage x = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = x.createGraphics();
g.setColor(new Color(255, 0, 0));
g.setStroke(new BasicStroke(2));
// draw a cross; where the lines cross the color will (logically) be darker
g.drawLine(0, 0, 100, 100);
g.drawLine(0, 100, 100, 0);
g.dispose();
// create a new BufferedImage with alpha channel
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
g = img.createGraphics();
// Draw opaque image using translucency.
g.setComposite(
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g.drawImage(x, 0, 0, null);
g.dispose();
// use the image somehow, in this case write it to file
writeImage(img, "test.png");
}
private static void writeImage(BufferedImage img, String fileName) {
try {
ImageIO.write(img, "png", new File(fileName));
}
catch (IOException e) {
System.out.println(e);
}
}
}
I'm currently working on a project with the theme of earth hour, and we are only allowed to use rectangles, circles and triangles. Here's the image i'm tring to create (not exactly, mine will be much more simplified!):
https://www.google.com/search?q=earth+hour&biw=1366&bih=586&source=lnms&tbm=isch&sa=X&ved=0ahUKEwj__5H0vtvQAhXLrlQKHTi8BagQ_AUIBygC#imgrc=fQkBxn0a8LnwbM%3A
(not sure if you could see the link)
But when i'm coding it, i'm running into trouble to rotate those rectangles to stand on the tangent line of the circle. I'm a student just learnt some basics of java, like loops and arrays. So my quesiton is that if there's some understandable way that doesn't involve some complex and exotic methods that could rotate those rectangles? I know it will probably involve some complicated solutions that is beyond my knowledge. But any help is much appreciated.
this is part of the code that i build the building standing perpendicularly to the circle(earth):
// create mid buildings
Color blc = new Color(0, 0, 0);
Rectangle midBld = new Rectangle(240, 220, 20, 40, blc);
midBld.draw(g);
Rectangle midBld1 = new Rectangle(242, 190, 16, 30, blc);
midBld1.draw(g);
Triangle midBld2 = new Triangle(250, 160, 8, 30, blc);
midBld2.draw(g);
Triangle midBld3 = new Triangle(250, 160, -8, 30, blc);
midBld3.draw(g);
A Rectangle cannot be rotated, its edges are always in parallel to the axis. But you can rotate and translate the coordinate system in witch you draw the shapes. From Graphics2D API doc.
All coordinates passed to a Graphics2D object are specified in a device-independent coordinate system called User Space, which is used by applications. The Graphics2D object contains an AffineTransform object as part of its rendering state that defines how to convert coordinates from user space to device-dependent coordinates in Device Space.
Graphics2D also provide two methods that are useful in this task: translate that moves the origin of the coordinates and rotate that, well, rotates the system.
package graphics;
import javax.swing.*;
import java.awt.*;
/**
* Earth Hour
*/
public class RotateRect extends JFrame {
private static final int WIDTH = 400;
private static final int HEIGHT = 400;
public RotateRect() {
this.setSize(WIDTH, HEIGHT);
this.setTitle("Rotate Rectangles");
this.setContentPane(new JPanel() {
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// Background: White
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, this.getWidth(), this.getHeight());
// Draw "Earth": Center(200, 400), Radius=200
g2.setColor(Color.BLACK);
g2.fillOval(0, 200, 400, 400);
// Move origin to center of the canvas (surface of earth)
g2.translate(200, 200);
// Rotate the coordinate system, relative to the center of earth.
// note x, y are in the translated system
// Transforms are accumulative
g2.rotate(-Math.PI/6, 0, 200);
// Fill a rectangle with top-left corner at (-20, 80) in the rotated system
// It's important to make the rectangle symmetrical to the y-axis, otherwise the building looks
// funny.
// Also, make the building "sunk" a little, so that it's fully on the ground.
g2.fillRect(-20, -80, 40, 100);
g2.rotate(Math.PI/3, 0, 200);
g2.fillRect(-20, -80, 40, 100);
g2.rotate(-Math.PI/6, 0, 200);
g2.fill(new Rectangle(-20, -80, 40, 100));
}
});
}
public static void main(String [] args) {
RotateRect rr = new RotateRect();
rr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
EventQueue.invokeLater(()->rr.setVisible(true));
}
}
I have the following code. While it displays the lines, rectangle, oval, and string correctly the image does not load. The image is in the correct directory, just cannot figure out why it isn't displaying...
import java.awt.*; // for Graphics, Image, and Color classes
import java.applet.Applet;
public class GraphicsDemo extends Applet
{
public void paint (Graphics g)
{
Image image;
image = this.getImage(getDocumentBase (), "flower.jpg");
// display smaller complete image in upper left corner of window
g.drawImage(image, 0, 0, 427, 284, // destination topL, botR
0, 0, 640, 427, this); // source topL, botR
// establish color of all lines to be drawn
g.setColor(Color.BLUE);
// draw rectangle around region to be expanded
g.drawRect(200, 60, 120, 120); // topL, width & height
// draw lines between corners of rectangles
g.drawLine(200, 60, 240, 240); // upper left
g.drawLine(320, 60, 600, 240); // upper right
g.drawLine(200, 180, 240, 600); // lower left
g.drawLine(320, 180, 600, 600); // lower right
// display expanded part of original image
g.drawImage(image, 240, 240, 600, 600, // destination topL, botR
300, 90, 480, 270, this); // source topL, botR
// draw rectangle around expanded part of image
g.drawRect(240, 240, 360, 360); // topL, width & height
// create BLUE colored oval and write name on it
g.fillOval(520, 380, 45, 30); // topL, width & height
g.setColor(Color.WHITE); // change color for text
g.drawString("Max", 530, 400); // string & start position
} // end main
} // end class GraphicsDemo
More than likely the image does not exist in the document base. The rest of the code is functionally ok. Add the an init method as follows:
public void init() {
System.out.println(getDocumentBase());
}
and copy the image there to the location displayed.
Some related notes:
You're missing this statement from paint
super.paint(g);
Also would suggest moving this statement
image = this.getImage(getDocumentBase (), "flower.jpg");
to the applet's init method so that the image is not being loaded every time the applet is painted.
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)
(this is java) I have an oval, representing a unit. I want the colour of the oval to represent the unit's health. So a perfectly healthy unit will be all green. and with the unit's health decreasing the oval starts filling with red from the bottom. so, on 50% health the oval would be red in bottom half and green in the top half, and fully red when the unit's dead.
I'm sure the solution here must be obvious and trivial , but I just can't see it.
thanks a lot
You can draw a red oval in the background, then draw a green intersection of an oval and a rectangle, where the rectangle starts below the oval, then moves further to the top to reveal more of the red oval beneath.
You might like to read up on how to construct complex shapes out of primitives here
Override the paint method something like this:
public void paint(Graphics graphics)
{
super.paint(graphics);
Rectangle originalClipBounds = graphics.getClipBounds();
try
{
graphics.clipRect(100, 100, 100, 25);
graphics.setColor(Color.RED);
graphics.fillOval(100, 100, 100, 100);
}
finally
{
graphics.setClip(originalClipBounds);
}
try
{
graphics.clipRect(100, 125, 100, 75);
graphics.setColor(Color.BLUE);
graphics.fillOval(100, 100, 100, 100);
}
finally
{
graphics.setClip(originalClipBounds);
}
}
Might want to enhance it with some double buffering but you get the gist.
You can set the clip on the graphics when you draw the green. Only things within the clip actually get painted.
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g.create();
g2d.setColor(Color.RED);
g2d.fillOval(10, 10, 200, 100);
g2d.setColor(Color.GREEN);
g2d.setClip(10, 10, 200, 50);
g2d.fillOval(10, 10, 200, 100);
}