How do I solve drawing XOR artefact problems in ScalaFX/JavaFX with BlendMode.Difference? - scala

I'm porting some code from Java to Scala and having problems with drawing artefacts when attempting "rubber banding" - i.e. drawing a rectangle that moves with the mouse pointer.
This was relatively simple to do in Java2D, but I'm having problems making it work in Scala/JavaFX.
I'm using Scala 2.10.2, JavaFX 2.2.0-b21 and , Java 1.7.0_06 Java HotSpot(TM) 64-Bit Server VM on OS/X 10.8.4.
graphicsContext2D.globalBlendMode = BlendMode.DIFFERENCE seems equivalent to Graphics2D.setXORMode() and it almost works, but it:
sometimes leaves faint traces of where the rectangle has been when filling a rectangle.
produces grey lines that don't undraw when stroking a rectangle unless the line width is an even integer.
sometimes leaves faint traces of where the rectangle has been when stroking a rectangle with a line width that is an even integer.
doesn't XOR properly with the background provided by the parent component.
The last item isn't what I expected, but I think I understand what it is doing (treating the undefined background in the Canvas as black, so that it XORs to white on draw, and black on undraw, even though it looked green to start with.)
This is a test case that shows the problem:
import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.paint.Color
import scalafx.Includes._
import scalafx.scene.canvas.{GraphicsContext, Canvas}
import scalafx.scene.layout.Pane
import scalafx.scene.input._
import scalafx.geometry.Rectangle2D
import scalafx.scene.transform.Affine
import scalafx.scene.effect.BlendMode
object Dragger {
var startX: Double = 0.0
var startY: Double = 0.0
var oldRectangle: Rectangle2D = null
def mouseReleased(event: MouseEvent) {
}
def mousePressed(event: MouseEvent) {
startX = event.x
startY = event.y
}
def mouseDragged(g2: GraphicsContext, event: MouseEvent) {
if (oldRectangle != null)
drawRectangle(g2, oldRectangle)
val x0 = math.min(startX, event.x)
val y0 = math.min(startY, event.y)
val newRectangle = new Rectangle2D(x0, y0, math.abs(event.x - startX), math.abs(event.y - startY))
drawRectangle(g2, newRectangle)
oldRectangle = newRectangle
}
def drawRectangle(g2: GraphicsContext, r: Rectangle2D) {
//g2.strokeRect(r.minX, r.minY, r.width, r.height) // <--- stroke instead of fill for grey lines that don't undraw
g2.fillRect(r.minX, r.minY, r.width, r.height)
}
}
object Test extends JFXApp
{
println("javafx.runtime.version: " + System.getProperties.get("javafx.runtime.version"))
println("java.runtime.version: " + System.getProperties.get("java.runtime.version"))
stage = new JFXApp.PrimaryStage {
title = "Hello Stage"
width = 600
height = 472
scene = new Scene {
fill = Color.LIGHTGREEN
root = new Pane {
content = new Canvas(600, 450) {
graphicsContext2D.setStroke(Color.BLUE)
graphicsContext2D.setFill(Color.BLUE)
graphicsContext2D.fillRect(4, 4, 592, 442)
graphicsContext2D.setTransform(new Affine)
graphicsContext2D.globalBlendMode = BlendMode.DIFFERENCE
graphicsContext2D.setStroke(Color.WHITE)
graphicsContext2D.setFill(Color.WHITE)
graphicsContext2D.setLineWidth(1) // <--- increase line width to 2 to fix stroked line undrawing
onMouseDragged = (event: MouseEvent) => {
Dragger.mouseDragged(graphicsContext2D, event)
}
onDragDetected = (event: MouseEvent) => {
//Drag complete
}
onMousePressed = (event: MouseEvent) => {
Dragger.mousePressed(event)
}
onMouseReleased = (event: MouseEvent) => {
Dragger.mouseReleased(event)
}
}
}
}
}
}
This screenshot shows the problem (this is with stroking and a 2 pixel line width) after moving the mouse around repeatedly:
Any help would be greatly appreciated.

You can use the JavaFX capabilities instead of doing the rectangle move your self.
you can use the setTranstalteX() and setTranslateY() method of the rectangle. see Oracle example in the Ensemble Sample-->Graphics-->Transforms-->Translate.
Here also the code from The Ensemble:
public class TranslateSample extends Application {
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root, 230,220));
//create 2 rectangles with different color
Rectangle rect1 = new Rectangle(90, 90, Color.web("#ed4b00", 0.75));
Rectangle rect2 = new Rectangle(90, 90, Color.web("#ed4b00", 0.5));
//translate second one
rect2.setTranslateX(140);
// rectangle with adjustable translate
Rectangle rect3 = new Rectangle(40, 130, 60, 60);
rect3.setFill(Color.DODGERBLUE);
rect3.setTranslateX(20);
rect3.setTranslateY(10);
//show the rectangles
root.getChildren().addAll(rect2, rect1, rect3);
//create arrow
Polygon polygon = createArrow();
polygon.setLayoutX(110);
polygon.setLayoutY(30);
polygon.setRotate(90);
root.getChildren().addAll(polygon);
}
public static Polygon createArrow() {
Polygon polygon = new Polygon(new double[]{
7.5, 0,
15, 15,
10, 15,
10, 30,
5, 30,
5, 15,
0, 15
});
polygon.setFill(Color.web("#ff0900"));
return polygon;
}
public double getSampleWidth() { return 230; }
public double getSampleHeight() { return 220; }
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}

Related

JavaFX AnimationTimer seems out of sync, untill window is resized

A little introduction: I've created a simple (for now) application which uses an AnimationTimer to update animations and draw objects to the Canvas. Everything runs smoothly and the timer adjusts its fps to the refresh-rate of my laptop (50/60Hz).
When I start the program however, there seems to be something wrong which causes my animations to appear 'jurky' or dropping frames, but the framerate stays a solid 60/50fps. Then when I resize the window for the first time (no difference how many), suddenly all the animations are super-smooth. After that, everything stays 'synced' no matter the resizes done.
What is the reason that the AnimationTimerstarts 'out-of-sync' until the window is resized and can it be prevented?
Update
Added a code example. The problem is mostly visible when on 50Hz, but also exist on 60Hz. Using Eclipse on Windows 10 (first code-share, may be to much/missing things).
public void start(Stage primaryStage) {
try {
pane = new Pane();
drawables = new ArrayList<>();
canvas = new Canvas(400,400);
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
GraphicsContext g = canvas.getGraphicsContext2D();
SimpleAnimatedCircle circle = new SimpleAnimatedCircle(20);
circle.setX(100);
circle.setY(50);
timer = new AnimationTimer() {
#Override
public void handle(long now) {
frameCount++;
if (System.currentTimeMillis() > frameStart + 500) {
System.out.println("FPS: " + frameCount*2);
frameStart = System.currentTimeMillis();
frameCount = 0;
}
for (Drawable drawable:drawables) {
drawable.update();
}
g.setFill(Color.DARKSLATEBLUE);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
circle.draw(g);
}
};
timer.start();
canvas.setOnMouseClicked((e) -> {
circle.start();
});
pane.getChildren().add(canvas);
Scene scene = new Scene(pane,400,400);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static class SimpleAnimatedCircle {
double diameter;
double x;
double y;
long startTime;
double diffY = 300; // Animated distance over y-axis.
double duration = 2000; // 2 second duration.
public SimpleAnimatedCircle(double diameter) {
this.diameter = diameter;
}
public void setX(double value) {
x = value;
}
public void setY(double value) {
y = value;
}
public void start() {
startTime = System.currentTimeMillis();
}
public void draw(GraphicsContext g) {
double animatedY = y;
// Update the animation.
if (System.currentTimeMillis() < startTime + duration) {
animatedY = y + (System.currentTimeMillis() - startTime) /
duration * diffY;
}
g.setFill(Color.ORANGE);
g.fillOval(x, animatedY, diameter, diameter);
}
}

JavaFX 8 Dynamic Node scaling

I'm trying to implement a scene with a ScrollPane in which the user can drag a node around and scale it dynamically. I have the dragging and scaling with the mouse wheel working as well as a reset zoom, but I'm having trouble with the calculations to fit the node to the width of the parent.
Here is my code as an sscce.
(works) Mouse wheel will zoom in and out around the mouse pointer
(works) Left or right mouse press to drag the rectangle around
(works) Left double-click to reset the zoom
(doesn't work) Right double-click to fit the width
If I zoom in or out or change the window size, the fit to width does not work.
If anyone can help me with the calculations to fit the node to the width of the parent, I would very much appreciate it.
EDITED:
I marked the method that is not working correctly. It is fitWidth(), which is invoked by right mouse button double-clicking.
I edited the text of the question for clarity and focus
Hopefully this is more clear now.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ZoomAndPanExample extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
AnchorPane.setTopAnchor(scrollPane, 10.0d);
AnchorPane.setRightAnchor(scrollPane, 10.0d);
AnchorPane.setBottomAnchor(scrollPane, 10.0d);
AnchorPane.setLeftAnchor(scrollPane, 10.0d);
AnchorPane root = new AnchorPane();
Rectangle rect = new Rectangle(80, 60);
rect.setStroke(Color.NAVY);
rect.setFill(Color.NAVY);
rect.setStrokeType(StrokeType.INSIDE);
group.getChildren().add(rect);
// create canvas
PanAndZoomPane panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
root.getChildren().add(scrollPane);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.3d;
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(60);
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(200), new KeyValue(translateXProperty(), getTranslateX() - x)),
new KeyFrame(Duration.millis(200), new KeyValue(translateYProperty(), getTranslateY() - y)),
new KeyFrame(Duration.millis(200), new KeyValue(myScale, scale))
);
timeline.play();
}
/**
* !!!! The problem is in this method !!!!
*
* The calculations are incorrect, and result in unpredictable behavior
*
*/
public void fitWidth () {
double scale = getParent().getLayoutBounds().getMaxX()/getLayoutBounds().getMaxX();
double oldScale = getScale();
double f = (scale / oldScale)-1;
double dx = getTranslateX() - getBoundsInParent().getMinX() - getBoundsInParent().getWidth()/2;
double dy = getTranslateY() - getBoundsInParent().getMinY() - getBoundsInParent().getHeight()/2;
double newX = f*dx + getBoundsInParent().getMinX();
double newY = f*dy + getBoundsInParent().getMinY();
setPivot(newX, newY, scale);
}
public void resetZoom () {
double scale = 1.0d;
double x = getTranslateX();
double y = getTranslateY();
setPivot(x, y, scale);
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY( double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures( PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
return onMouseClickedEventHandler;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale)-1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f*dx, f*dy, scale);
event.consume();
}
};
/**
* Mouse click handler
*/
private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton().equals(MouseButton.PRIMARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.resetZoom();
}
}
if (event.getButton().equals(MouseButton.SECONDARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.fitWidth();
}
}
}
};
}
}
I found the answer. I was looking at the wrong calculations, assuming it to be related to the translations. The real culprit was the calculation for the difference in scale. I simply changed this:
double f = (scale / oldScale)-1;
to this:
double f = scale - oldScale;

How does 3D picking work in JavaFX 8

Unfortunately I could not find any good tutorial for picking api.
Can anyone give me a brief tutorial about it or some useful links?
Update
world.setOnMouseClicked((event)->{
PickResult res = event.getPickResult();
System.out.println("res "+res);
//you can get a reference to the clicked node like this
if (res.getIntersectedNode() instanceof Node){
((Node)res.getIntersectedNode()).setTranslateZ(-50);
}
});
The code above only work on the first node that I click.
Does anyone know what is the problem?
It seems to work just fine without much understanding. My guess is it takes the element closest to the camera (ie. in front) in the 2D representation of where you clicked. It doesn't matter which face you click, it always gets the right node.
The code is very simple. I've made a scene with a Parent called world that has 3D elements.
package simple3dboxapp;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.PickResult;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Simple3dBoxApp extends Application {
public Rotate rx = new Rotate();
{ rx.setAxis(Rotate.X_AXIS); }
public Rotate ry = new Rotate();
{ ry.setAxis(Rotate.Y_AXIS); }
public Rotate rz = new Rotate();
{ rz.setAxis(Rotate.Z_AXIS); }
Translate t = new Translate();
private final PerspectiveCamera camera = new PerspectiveCamera(true);
{camera.getTransforms().addAll(t, rz, ry, rx);}
Label data = new Label();{data.setWrapText(true);data.setPrefHeight(200);}
public Parent createContent() {
Group root = new Group();
for(int i = 0; i<10; i++){
Box box = new Box(50, 50, 50);
Text text = new Text("Hi "+i);
ImageView iv = new ImageView("file:apple-touch-icon.png");
Image im = iv.getImage();
Material m = new PhongMaterial(Color.CYAN, im, null, null, null);
box.setMaterial(m);
box.getTransforms().add(new Translate(-250 + i * 50 , -100, -100 + i * 50));
text.getTransforms().add(new Translate(-250 + i * 50 , 0, -100 + i * 50));
iv.getTransforms().add(new Translate(-250 + i * 50 , +100, -100 + i * 50));
root.getChildren().addAll(box,text,iv);
}
camera.setNearClip(1);
camera.setFarClip(2000);
camera.setFieldOfView(100);
// Use a SubScene
SubScene subScene = new SubScene(root, 500,500);
subScene.setFill(Color.ALICEBLUE);
subScene.setCamera(camera);
Group group = new Group();
group.getChildren().add(subScene);
return group;
}
#Override
public void start(Stage stage) {
VBox vbox = new VBox();
Parent world = createContent();
world.setFocusTraversable(true);
vbox.getChildren().addAll(world,data);
Scene scene = new Scene(vbox);
stage.setScene(scene);
stage.show();
world.setOnKeyPressed((evt)->{
switch (evt.getCode()){
case UP:
rx.setAngle(rx.getAngle()+5);
break;
case DOWN:
rx.setAngle(rx.getAngle()-5);
break;
case RIGHT:
t.setX(t.getX()+10);
//camera.setTranslateX(camera.getTranslateX()+10);
break;
case LEFT:
t.setX(t.getX()-10);
//camera.setTranslateX(camera.getTranslateX()-10);
break;
case Z:
double zoom = evt.isShortcutDown()?-10:+10;
t.setZ(t.getZ()+zoom);
//camera.setTranslateZ(camera.getTranslateZ()+zoom);
break;
}
});
world.setOnMouseClicked((event)->{
PickResult res = event.getPickResult();
data.setText("res "+res);
if (res.getIntersectedNode() instanceof Box){
((Box)res.getIntersectedNode()).setMaterial(
new PhongMaterial(event.isShiftDown() ? Color.BLACK : Color.RED));
}
});
}
public static void main(String[] args) {launch(args);}
}
And this is the result from clicking a Box
res PickResult [node = Box#1dc02a4, point = Point3D [x = 12.361420184603276, y = 15.984395060669844, z = -5.0], distance = 1866.0254037844388, texCoord = Point2D [x = 0.6236142018460328, y = 0.8196879012133969]
PickResult javadoc

Filling a custom-shaped Clutter Actor with a Cairo-drawn canvas

Clutter 1.12
Cogl 1.10
Vala or C or Python.
I might have a fundamental misunderstanding here —
I think of "Actors" as 3D polygon things. I think of their colours as either vertex colors or as texture-mapping. In this light, I have been trying to draw a custom Actor and fill it with stuff drawn via Cairo. I'm not getting anywhere.
Code is included below (in Vala). Can anyone set me right about Clutter's basics (the docs just don't cut it) or, if I'm close, help me get that code working.
I expect to see a rounded rectangle with a smiley face within. What I'm seeing instead is the Cogl path fill covering* the face. I think the paint() is being done after the drawme()
*If you set the Clutter.Color to "#0001" in method paint(), you'll see this.
/*
Clutter Actor with custom paint() and filled with a Cairo-drawn texture.
(NOT yet working.)
Compile with:
valac \
--pkg clutter-1.0 \
--pkg cogl-1.0 \
somename.vala
*/
public class SmileyRect : Clutter.Actor {
//private vars
private Clutter.Canvas _canvas;
private bool _flip = false;
private int _w = 300;
private int _h = 300;
//Constructor
construct {
_canvas = new Clutter.Canvas();
_canvas.set_size( this._w, this._h );
this.set_size( this._w, this._h );
this.set_content( _canvas );
//Connect to the draw signal - this is as-per Clutter docs.
_canvas.draw.connect( drawme );
//Make it reactive and connect to the button-press-event
this.set_reactive( true );
this.button_press_event.connect( button_press );
}
/*
Button press signal handler.
Changes the colour of what will be painted on the canvas.
*/
private bool button_press ( Clutter.ButtonEvent evt ) {
this._flip = !this._flip; //Jiggle a value.
this.redraw(); // Forces re-run of the drawme() method.
return true; //all done with this signal.
}
//Common function to draw Cogl stuff - used in paint and pick.
private void draw_rr( Clutter.Color? color ) {
if (color != null ) { Cogl.set_source_color4ub(color.red,color.green,color.blue,color.alpha); }
Cogl.Path.round_rectangle( 0, 0, this._w, this._h, 15, 0.3f );
Cogl.Path.close();
// When from paint():
// Is there some way to fill this Cogl path with the contents
// of this._canvas? Or some other paradigm?
if (color != null ) { Cogl.Path.fill(); }
}
/* Some kind of freaky, this magic paint() thing.
Took me ages to get it running.
I want to draw a rounded rectangle as the basic shape
of this actor.
*/
public override void paint() {
stdout.printf("paint runs.\n");
// I did try a transparent color #0000 - just to see. No go.
// #000f - draws a black rounded rect *OVER* the Cairo canvas. It covers
// the smiley face.
this.draw_rr( Clutter.Color.from_string("#0000") );
}
/* I followed paint() example, but the argument was tricky.
I eventually found it from some PyClutter source code.
*/
public override void pick(Clutter.Color color) {
stdout.printf("pick runs.\n");
this.draw_rr( color );
}
/*
Draws the Cairo art to the canvas.
I want this art to be the bitmap/pixmap that *fills* the
basic rounded rectangle shape of this actor.
i.e I want the smile face cairo rectangle to be *within* the
polygon that is draw via Cogl in paint() method.
Does this even make sense?
*/
private bool drawme( Cairo.Context ctx, int width, int height) {
//Draw a rectangle
double[] col;
if (this._flip) {
col = {1f,0f,0f};
}
else {
col = {0f,1f,0f};
}
ctx.set_source_rgb( col[0], col[1], col[2] );
ctx.rectangle( 0, 0, width, height );
ctx.fill();
//Draw a smiley face.
// Aside: Been trying to avoid all the weird casting of the floats/doubles used below
// (see the generated c source.) Not at all sure what's going on there.
// Also not sure about that 'f' on the numbers. Where/when do I have to use it?
double pi = 3.14f;
double w = (double) width;
double h = (double) height;
ctx.set_line_width( 6f );
ctx.set_source_rgb( 0f, 0f, 0.8f );
//eyes
ctx.move_to( w/3f, h/3f );
ctx.rel_line_to( 0f, h/6f );
ctx.move_to( 2*(w/3f), h/3f );
ctx.rel_line_to( 0f, h/6f );
ctx.stroke();
ctx.set_source_rgb( 1f, 1f, 0f );
double rad = (w > h) ? h : w;
//face
ctx.arc( w/2f, h/2f, (rad/2f) - 20f,0f,2f * pi );
ctx.stroke();
//smile
ctx.arc( w/2f, h/2f, (rad/3f) -10f, pi/3f, 2f * (pi/3f) );
ctx.stroke();
return true;
}
//Redraw - forces invalidate which trips the draw event
public void redraw() {
this._canvas.invalidate();
}
} //end SmileyRect class
void main( string[] args ) {
Clutter.init(ref args);
var stage = new Clutter.Stage();
stage.set_size(400,400);
var rs = new SmileyRect();
stage.add_child(rs);
rs.redraw();
stage.destroy.connect(Clutter.main_quit);
stage.show();
Clutter.main();
}

Drawing in Canvas with GWT

I have two images that i want to draw into a canvas object. I got those images from a server and when the loadHandler invokes, i get the dimensions of the image (they have the same width and height) and calculate the dimensions of the canvas. Then i draw each image, at the calculated x,y position in canvas. The problem is that only one image appears in canvas. Why?
Here is a part of the code:
final Image siImg = new Image();
siImg.setVisible(false);
siImg.setUrl(Constants.URL_PREFIX + siPath);
siImg.addLoadHandler(new LoadHandler() {
#Override
public void onLoad(LoadEvent event) {
int siWidth = siImg.getWidth();
int siHeight = siImg.getHeight();
siImg.removeFromParent();
if (!CategoryTableView.this.dimFromBg) {
CategoryTableView.this.width = siWidth;
CategoryTableView.this.height = siHeight * sSize;
//CategoryTableView.this.setPixelSize(CategoryTableView.this.width, CategoryTableView.this.height);
CategoryTableView.this.canvas.setPixelSize(CategoryTableView.this.width, CategoryTableView.this.height);
CategoryTableView.this.canvas.setCoordinateSpaceHeight(CategoryTableView.this.height);
CategoryTableView.this.canvas.setCoordinateSpaceWidth(CategoryTableView.this.width);
CategoryTableView.this.dimFromBg = true;
}
ImageElement imageElement = (ImageElement) siImg.getElement().cast();
int left = xOff;
int top = yOff + (siHeight * fi);
CategoryTableView.this.context.drawImage(imageElement, left, top);
}
});
RootPanel.get().add(siImg);
Ok i think i find it...i have to save the context's state each time. Is that right? (Because it works now!)
You add the image to your root panel in the last line
final Image siImg = new Image();
...
RootPanel.get().add(siImg);
instead of adding your canvas. So you will only see the image instead of the canvas. You have to add the Canvas to your root panel and draw both images to your canvas. For performance-reasons it is better to draw to a backbuffer instead of drawing directly to the canvas. Here is a little example:
Canvas canvas = Canvas.createIfSupported();
Canvas backBuffer = Canvas.createIfSupported();
Context2d context = canvas.getContext2d();
Context2d backBufferContext = backBuffer.getContext2d();
Image image1 = new Image("http://your.url.to/image.jpg");
image1.addLoadHandler(new LoadHandler() {
public void onLoad(LoadEvent event) {
// do anything you want here
doDraw();
}
});
Image image2 = new Image("http://your.url.to/image2.jpg");
image2.addLoadHandler(new LoadHandler() {
public void onLoad(LoadEvent event) {
// do anything you want here
doDraw();
}
});
RootPanel.get().add(canvas);
And the draw-method would look like this:
public void doDraw() {
backBufferContext.setFillStyle(redrawColor);
backBufferContext.fillRect(0, 0, width, height);
ImageElement imageElement = ImageElement.as(image1.getElement());
backBufferContext.drawImage(imageElement, 0, 0, 1024, 768, 0, 0, 102, 76);
ImageElement imageElement = ImageElement.as(image2.getElement());
backBufferContext.drawImage(imageElement, 0, 0, 1024, 768, 102, 76, 102, 76);
context.drawImage(backBufferContext.getCanvas(), 0, 0);
}
Please note: You have to use global variables in this example. Change this to your needs by either passing the arguments/classes or defining the variables class-wide. Also the drawing-areas are hardcoded in this example: this you have also to change to your needs.