How to draw an 1 pixel line using Javafx Canvas? - java

I've been googling and searching, found some some related questions/posts but none of the address my problem.
I am drawing lines directly on canvas (JavaFX) using:
gc.setStroke(color);
gc.setLineWidth(lineWidth);
gc.strokeLine(startX, startY, endX, endY);
I want 1 pixel width lines. So I set lineWidth=1.
I get this:
Note that the lines are blurred. It is not 1 pixel.
I've tried to set lineWidth to 0.1 or 0.01, etc. It does not change the result.
By the way... I do not understand why this parameter is a double. I read somewhere that it has to do with DPI. But I do not understand what is the unit and how it is converted to pixels.
Oracle's documentation does not help. (or I did not find the one that helps)
I'd like to get this instead:
This was implemented in another platform. Note that lines are sharp and have just one 1 pixel.

Imagine each pixel as a (small) rectangle (instead of a point). The integer coordinates are the boundaries between pixels; so a (horizontal or vertical) line with integer coordinates falls "between pixels". This is rendered via antialising, approximating half of the line on one pixel and half on the other. Moving the line 0.5 pixels left or right moves it to the center of the pixel, getting around the issue.
Here's a sample:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SharpCanvasTest extends Application {
#Override
public void start(Stage primaryStage) {
Canvas sharpCanvas = createCanvasGrid(600, 300, true);
Canvas blurryCanvas = createCanvasGrid(600, 300, false);
VBox root = new VBox(5, sharpCanvas, blurryCanvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private Canvas createCanvasGrid(int width, int height, boolean sharp) {
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D() ;
gc.setLineWidth(1.0);
for (double x = sharp ? 0.5 : 0.0; x < width; x+=10) {
gc.moveTo(x, 0);
gc.lineTo(x, height);
gc.stroke();
}
for (double y = sharp ? 0.5 : 0.0; y < height; y+=10) {
gc.moveTo(0, y);
gc.lineTo(width, y);
gc.stroke();
}
return canvas ;
}
public static void main(String[] args) {
launch(args);
}
}
And the results:

Use coordinates in this notation x.5.
Look my example:
gc.setFill(Color.BLACK);
gc.setLineWidth(1.0);
gc.strokeRect(50, 100, 25.0, 25.0);
gc.strokeRect(100.5, 100.5, 25.0, 25.0);
You will get two squares, the second sharp.
Reference: https://dlsc.com/2014/04/10/javafx-tip-2-sharp-drawing-with-canvas-api/

Related

Applying Reflection to DropShadow Effect in JavaFX

Using JavaFX, I am trying to draw text onto a canvas with a drop shadow and a reflection effect chained together.
The following code will display red text that is reflected, and then a drop shadow is applied to the original and reflected text.
Canvas canvas = new Canvas(400,400);
GraphicsContext context = canvas.getGraphicsContext2D();
context.setFont( new Font("Arial Bold", 48) );
context.setFill(Color.RED);
DropShadow shadow = new DropShadow(6, 2, 2, Color.BLACK);
Reflection reflect = new Reflection(10, 1.0, 1.0, 0.0);
shadow.setInput(reflect);
context.setEffect(shadow);
context.fillText("Hello, world!", 100,100);
However, the drop shadow appears "backwards" in the reflection, because the shadow needs to be applied first for a realistic effect. I tried to accomplish this by reversing the order in which the effects are applied, by changing the setInput and setEffect lines of code from above as follows:
reflect.setInput(shadow);
context.setEffect(reflect);
However, the result is that only the reflection is applied; I can not see any drop shadow at all.
Why isn't the drop shadow being applied / not visible?
How can I rewrite this code to achieve the desired effect (only using composition of effects, if possible)?
I don't know if what you say can be achieved by using GraphicsContext with standard API, so other answers are welcome. However, this might be a temporary workaround, provided that it is actually what you needed. The shadow is applied first and then the image is copied pixel by pixel to imitate the reflection effect. Please see the screenshot below: (original - on the left, new - on the right).
Full working example is attached below. It requires some tweaking to get a general solution but there should be enough to start with.
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class FXApp extends Application {
private Parent createContent() {
Canvas canvas = new Canvas(400,400);
Font font = new Font("Arial Bold", 48);
Color fill = Color.RED;
DropShadow shadow = new DropShadow(6, 2, 2, Color.BLACK);
fillTextWithReflection(canvas.getGraphicsContext2D(), "Hello, world!", 100, 100, font, fill, shadow);
return new Pane(canvas);
}
private void fillTextWithReflection(GraphicsContext g, String text, double textX, double textY, Font font, Color fill, Effect effect) {
Text t = new Text(text);
t.setFont(font);
// 5 px margin
Canvas tmpCanvas = new Canvas(t.getLayoutBounds().getWidth() + 5, t.getLayoutBounds().getHeight() + 5);
// set configuration
GraphicsContext tmpContext = tmpCanvas.getGraphicsContext2D();
tmpContext.setFont(font);
tmpContext.setFill(fill);
tmpContext.setEffect(effect);
// draw on temporary context
tmpContext.fillText(text, 0, font.getSize());
// take a snapshot of the text
WritableImage snapshot = tmpCanvas.snapshot(null, null);
int w = (int)snapshot.getWidth();
int h = (int)snapshot.getHeight();
WritableImage reflected = new WritableImage(w, h);
// make an 'inverted' copy
for (int y = 0; y < h; y++) {
// imitate fading out of reflection
double alpha = y / (h - 1.0);
for (int x = 0; x < w; x++) {
Color oldColor = snapshot.getPixelReader().getColor(x, y);
Color newColor = Color.color(oldColor.getRed(), oldColor.getGreen(), oldColor.getBlue(), alpha);
reflected.getPixelWriter().setColor(x, h - 1 - y, newColor);
}
}
// draw on the actual context
// images are drawn from x, y top-left but text is filled from x, y + h
// hence corrections
// this can be replaced with actual fillText() call if required
g.drawImage(snapshot, textX, textY - font.getSize());
g.drawImage(reflected, textX, textY + h - font.getSize());
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
}

Polygon is created far away the specified position, LibGDX

I've been looking for solutions in google with no success, I'm creating a small library (just a wrapper) for Box2D in LibGDX and I'm drawing a texture for each body, taking as base the Body.getPosition() vector, however, I see polygon's getPosition() is different respect to CircleShapes and the walls (which were created with setAsBox() method).
Here's an image:
http://i.stack.imgur.com/NzooG.png
The red points are the center of mass, the cyan circles are the geometric center (right?) given by body.getPosition(), as you can see I can adapt the texture to the body in terms of position, rotation and scale but this does not happen with polygons (except the ones made with setAsBox()).
Basically, what I want is to get the cyan circle in the AABB centre of the regular polygons. here's a runnable example:
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.math.Vector2;
public class MyGdxGame extends ApplicationAdapter {
Tabox2D t;
float w ,h;
#Override
public void create () {
w = Gdx.graphics.getWidth();
h = Gdx.graphics.getHeight();
t = Tabox2D.getInstance();
t.debug = true;
t.newBall("d", 100, 200, 25);// Ball.
t.newBox("s", 10, 10, w - 20, 50);// Floor.
t.newHeptagon("d", new Vector2(200, 200), 40);
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
t.update(0);// Don't move anything just to see the cyan circle.
t.draw();
}
}
Tabox2D class is here: https://github.com/tavuntu/tabox2d
NOTE: this was tested with the last version of Android Studio and LibGDX.
Thanks in advance!
Looks to me like you are positioning the shapes on the centre point, instead of the body. The centre of the shapes should be 0,0, not center.x,y. Fixture positions are relative to the body.
OK, so I think I solved this maybe in an inelegant way. What I did was:
Create the polygons points around the origin
Get AABB center of polygon and centroid (not necessarily the same)
Translate points to make AABB center the shape center, so the image can be drawn respect to this and not the center of mass.
The code needs a lot of cleaning but it seems to work just well, updated code in github repo, thanks!
The change was basically translate points from the original centroid to AABB center:
for(int i = 0; i < pts.length - 1; i += 2) {
pts[i] -= aabbCenter.x;
pts[i + 1] -= aabbCenter.y;
}

ShapeRenderer produces pixelated shapes using LibGDX

When I use a ShapeRenderer, it always comes out pixelated. But if I draw the shape in photoshop with the same dimensions, it's very smooth and clean-looking.
My method is just as follows:
package com.me.actors;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.scenes.scene2d.Actor;
public class bub_actors extends Actor {
private ShapeRenderer shapes;
private Texture text;
private Sprite sprite;
public bub_actors(){
shapes = new ShapeRenderer();
text = new Texture(Gdx.files.internal("data/circle.png"));
sprite = new Sprite();
sprite.setRegion(text);
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
batch.draw(sprite, 200, 200, 64, 64);
shapes.begin(ShapeType.FilledCircle);
shapes.filledCircle(50, 50, 32);
shapes.setColor(Color.BLACK);
shapes.end();
}
}
Here's an image of the output:
Any ideas as to why this happens? Is it possible to make the ShapeRenderer look like the image (so I don't have to create a SpriteBatch of different-colored circles...).
The difference is anti-aliasing that Photoshop applies to the image it generates. If you zoom in on the edges of the two circles, you'll see the anti-aliased one has some semi-black pixels around the edge, where the ShapeRenderer generated circle just shows pixels entirely on or off.
The Libgdx ShapeRenderer was designed for being a quick and simple way to get debugging shapes on the screen, it does not support anti-aliasing. The easiest way to get consistent anti-aliased rendering it to use a texture. (Its also possible with an OpenGL shader.)
That said, you do not have to create different sprites just to render different colored circles. Just use a white circle with a transparent background, and then render it with a color. (Assuming you want a variety of solid-colored circles).
Here is really simple way to achieve smooth & well-looking shapes without using a texture and SpriteBatch.
All you have to do is to render couple of shapes with slightly larger size and
lower alpha channel along with the first one.
The more passes the better result, but, of course, consider ppi of your screen.
...
float alphaMultiplier = 0.5f; //you may play with different coefficients
float radiusStep = radius/200;
int sampleRate = 3;
...
//do not forget to enable blending
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapeRenderer.begin(ShapeType.Filled);
//first rendering
shapeRenderer.setColor(r, g, b, a);
shapeRenderer.circle(x, y, radius);
//additional renderings
for(int i=0; i<sampleRate; i++) {
a *= alphaMultiplier;
radius += radiusStep;
shapeRenderer.setColor(r, g, b, a);
shapeRenderer.circle(x, y, radius);
}
shapeRenderer.end();
...
Here is a screenshot of what can you achieve.
If you're not teetering on the edge of losing frames, you can enable antialiasing in your launcher. You can increase the sample count for better results, but it's really diminishing returns.
LWJGL3 : config.setBackBufferConfig(8, 8, 8, 8, 16, 0, 2);
LWJGL2 : config.samples = 2;
GWT : config.antialiasing = true;
Android: config.numSamples = 2;
iOS : config.multisample = GLKViewDrawableMultisample._4X;

Join Two Sphere with a Cylinder using JavaFX8 3D

Please I want to draw a 3D graph using JavaFX 8 (3D). I already know some basics of 3D like draw sphere, firstly coloring the sphere and add shadow, then some light and initialisation of the sphere. My problem is I want to join the spheres by using a cylinder, but if there are for example 2 cylinders between two spheres it must be an arc or curved cylinder (I don't know if this is possible). I already tried that but nothing appears, even if something appears it's just a cylinder (not like a line just small).
Another problem, I want to know how the rotation can help in such a situation.
And last question, is it possible to make a scrollbar or just using event of Zoom? Thanks.
the picture taken from : Here
This is my code:
import java.util.ArrayList;
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
public class Graphe3D extends Application {
Group root;
PhongMaterial material;
ArrayList<Sphere> sphere;
#Override
public void start(Stage primaryStage) {
sphere=new ArrayList<>();
root=new Group();
material= new PhongMaterial();
for(int i=0;i<3;i++){
sphere.add(new Sphere(50));
//Sphere Color
material.setDiffuseColor(Color.RED);
//Shadow Color
material.setSpecularColor(Color.rgb(30, 30, 30));
//Init Sphere
sphere.get(i).setMaterial(material);
sphere.get(i).setTranslateX(new Random().nextInt(600));//set location X,Y and Z
sphere.get(i).setTranslateY(new Random().nextInt(600));
sphere.get(i).setTranslateZ(50); // ?
sphere.get(i).setDrawMode(DrawMode.FILL);
sphere.get(i).setCullFace(CullFace.BACK);// ?
//Create Light
PointLight pointLight = new PointLight(Color.ANTIQUEWHITE);
pointLight.setTranslateX(800);
pointLight.setTranslateY(-100);
pointLight.setTranslateZ(-1000);
root.getChildren().add(pointLight); //ajout de lumiere
root.getChildren().add(sphere.get(i)); //ajout des spheres au scene(root)
}
//Display
Scene scene = new Scene(root);
scene.setFill(Color.rgb(10, 10, 40));
scene.setCamera(new PerspectiveCamera(false));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The molecule sample application from Oracle does much of what you are asking. Read the linked tutorial and study the source code provided for the sample application.
Regarding a couple of your questions.
if there is for example 2 cylinder between two sphere it must an arc or curved cylinder
This is possible. You would have to generate a custom TriangleMesh rather than using the pre-built Cylinder class. Essentially, you need to create an elliptical Torus and only display an arc portion of the Torus between your two nodes. I will not provide detailed instructions on how to do this in the context of a StackOverflow answer.
I want to know how the rotation can help in such situation and last question is it possible to make a scrollbar or just using event of Zoom ?
Study the Molecule sample code linked earlier as that has rotation and zoom capability.
You can calculate the cylinder position and rotation using
Shape.getTransforms().add(new Rotate(angle, x, y, z, Rotate.Z_AXIS))
and
Math.toDegrees(Math.atan2(y, x))
Here's a sample working in 2D
Sphere var = null;
for(Sphere sphere: myListSphere {
if(var!=null) {
double x = sphere.getTranslateX()-var.getTranslateX();
double y = sphere.getTranslateY()-var.getTranslateY();
//the distance from each sphere
Cylinder cyl = new Cylinder(5, Math.sqrt((x*x)+(y*y)), 5);
cyl.setMaterial(this.redMaterial);
cyl.setTranslateX(sphere.getTranslateX()-(x/2));
cyl.setTranslateY(sphere.getTranslateY()-(y/2));
//the angle from both dots with Math.atan
cyl.getTransforms().add(new Rotate(90+Math.toDegrees(Math.atan2(y, x)), 0, 0, 0, Rotate.Z_AXIS));
}
var = shape;
}

Make oval/rectangle using float/double values

I want to draw a figure using float or double values, to be precise.
I use:
g.drawOval(0, 0, 10, 10);
to draw a circle, but I only can use integer values.
Is there any statement that use float/double values that do the same?
Here is a picture: Problem
The circles have to be centered, and I can't. Any solution?
Code:
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JPanel;
public class Bulls_EYE extends JPanel
{
int red, green, blue;
int height, width;
int heightOval = 475, widthOval = 475;
Random rValue = new Random();
public void paint (Graphics g)
{
super.paint(g);
for (int idx = 0; idx < 100; idx++)
{
g.setColor(new Color(red = 1 + rValue.nextInt(255), green = 1 + rValue.nextInt(255), blue = 1 + rValue.nextInt(255)));
g.fillOval(width+2*idx, height+2*idx, widthOval-5*idx, heightOval-5*idx);
}
}
}
I think it's an interesting question but needs more context. Drawing primitives are usually expressed in pixel coordinates so fractions of a pixel do not make much sense.
If you want precision like a CAD application note that what is displayed on the screen is only an approximation of the underlying model due to the limitations of the display.
You can represent your models precisely in memory (with limitations in floating point representation) and draw the approximation on the screen.
Update
Based on your last update:
We know from the JavaDoc that fillOval takes as parameters (x, y, w, h) where x, y are the upper left coordinates, and w, h are the width and height.
If for each concentric circle you move the upper left coordinates inward, in this case by 2 px, to keep them centered, you must also reduce the width and height by twice that amount. Change the following line:
g.fillOval(width+2*idx, height+2*idx, widthOval-5*idx, heightOval-5*idx);
To
int dx, dy, dw, dh;
dx = 2*idx;
dy = 2*idx;
dw = 2*dx; // note this is 4*idx not 5*idx like you have currently
dh = 2*dy;
g.fillOval(width+dx, height+dy, widthOval-dw, heightOval-dh);
Note that your width and height variables being used in the first and second parameters really doesn't have anything to do with width and height but instead are providing a beginning offset from the origin where the oval is drawn.
There is no reason you should do this, because when drawing an oval with the given coordinates, they are referred to pixels on the screen. Since you can't draw between pixels, 1 is the smallest unit you can use. If you want to round the values before drawing, you can use
g.drawOval(Math.round(a),Math.round(b),Math.round(x),Math.round(y)
which will round the float a, b, x and y before drawing the oval. The only reason I can see is that you calculate the coordinates and the result is a float, then you need to round it like above.
You can use the Arc2D class for drawing circles with float/double precision, since it is a Shape and the Graphics2D class can draw shapes.
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Shape circle = new Arc2D.Double(
// Same values as used in the fillOval function,
// but with double precision.
x, y, width, height,
// Draw a full circle (yes, in degrees).
0, 360,
// Connect the endpoint with the startpoint.
Arc2D.CORD
);
// Paint the circle.
g2d.fill(circle);
}
In a similar way, you can draw rectangles by using the Rectangle2D class.
Also, please use the paintComponent function instead of the paint function, as explained here.

Categories

Resources