I am trying to create a simple Bitmap class using JavaFX which allows me to load, use, modify and save a bitmap to file (for a scientific simulator). I have problem loading the image into a byte array.
1- Am I correct that each pixel will need 3 bytes space in the buffer?
2- getPixels function receives the following exception:
java.lang.ClassCastException: class javafx.scene.image.PixelFormat$ByteRgb cannot be cast to class javafx.scene.image.WritablePixelFormat (javafx.scene.image.PixelFormat$ByteRgb and javafx.scene.image.WritablePixelFormat are in unnamed module of loader 'app')
The cast to (WritablePixelFormat) was suggested by Intellij. What am I doing wrong?
3- How can I use getPixels function to load the whole image into a 2D array of RGB integers? (to make working with pixels easier)
Thanks.
public class BMP
{
byte[] buffer;
int width;
int height;
public BMP()
{
}
public void load(String filename) throws FileNotFoundException
{
//Creating an image
Image image = new Image(new FileInputStream(filename));
this.width = (int)image.getWidth();
this.height = (int)image.getHeight();
this.buffer = new byte[width * height * 3];
//Reading color from the loaded image
PixelReader pixelReader = image.getPixelReader();
//Reading pixels of the image
/*
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
//Retrieving the color of the pixel of the loaded image
Color color = pixelReader.getColor(x, y);
System.out.println(color.toString());
}
}*/
pixelReader.getPixels(
0,
0,
width,
height,
(WritablePixelFormat<ByteBuffer>) PixelFormat.getByteRgbInstance(),
buffer,
0,
width * 3
);
}
public static void main(String[] args)
{
BMP bmp1 = new BMP();
try
{
bmp1.load("e:/1.bmp");
System.out.println("Width:"+ bmp1.width + " length:" + bmp1.height);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
}
}
With your loaded image and its PixelReader you can construct a WritableImage which will provide you with a PixelWriter. That should be sufficient to work with the image. I would not extract that into an array.
Related
I have a sample 3D application (built by taking reference from the Javafx sample 3DViewer) which has a table created by laying out Boxes and Panes:
The table is centered wrt (0,0,0) coordinates and camera is at -z position initially.
It has the zoom-in/out based on the camera z position from the object.
On zooming in/out the object's boundsInParent increases/decreases i.e. area of the face increases/decreases. So the idea is to put more text when we have more area (always confining within the face) and lesser text or no text when the face area is too less. I am able to to do that using this node hierarchy:
and resizing the Pane (and managing the vBox and number of texts in it) as per Box on each zoom-in/out.
Now the issue is that table boundsInParent is giving incorrect results (table image showing the boundingBox off at the top) whenever a text is added to the vBox for the first time only. On further zooming-in/out gives correct boundingBox and does not go off.
Below is the UIpane3D class:
public class UIPane3D extends Pane
{
VBox textPane;
ArrayList<String> infoTextKeys = new ArrayList<>();
ArrayList<Text> infoTextValues = new ArrayList<>();
Rectangle bgCanvasRect = null;
final double fontSize = 16.0;
public UIPane3D() {
setMouseTransparent(true);
textPane = new VBox(2.0)
}
public void updateContent() {
textPane.getChildren().clear();
getChildren().clear();
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
textPane.setTranslateY(getHeight() / 2 - textPane.getHeight() / 2.0);
bgCanvasRect = new Rectangle(getWidth(), getHeight());
bgCanvasRect.setFill(Color.web(Color.BURLYWOOD.toString(), 0.10));
bgCanvasRect.setVisible(true);
getChildren().addAll(bgCanvasRect, textPane);
}
public void resetInfoTextMap()
{
if (infoTextKeys != null || infoTextValues != null)
{
try
{
infoTextKeys.clear();
infoTextValues.clear();
} catch (Exception e){e.printStackTrace();}
}
}
public void updateInfoTextMap(String pKey, String pValue)
{
int index = -1;
boolean objectFound = false;
for (String string : infoTextKeys)
{
index++;
if(string.equals(pKey))
{
objectFound = true;
break;
}
}
if(objectFound)
{
infoTextValues.get(index).setText(pValue.toUpperCase());
}
else
{
if (pValue != null)
{
Text textNode = new Text(pValue.toUpperCase());
textNode.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, fontSize));
textNode.wrappingWidthProperty().bind(widthProperty());
textNode.setTextAlignment(TextAlignment.CENTER);
infoTextKeys.add(pKey);
infoTextValues.add(textNode);
}
}
}
}
The code which get called at the last after all the manipulations:
public void refreshBoundingBox()
{
if(boundingBox != null)
{
root3D.getChildren().remove(boundingBox);
}
PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.web(Color.CRIMSON.toString(), 0.25));
Bounds tableBounds = table.getBoundsInParent();
boundingBox = new Box(tableBounds.getWidth(), tableBounds.getHeight(), tableBounds.getDepth());
boundingBox.setMaterial(blueMaterial);
boundingBox.setTranslateX(tableBounds.getMinX() + tableBounds.getWidth()/2.0);
boundingBox.setTranslateY(tableBounds.getMinY() + tableBounds.getHeight()/2.0);
boundingBox.setTranslateZ(tableBounds.getMinZ() + tableBounds.getDepth()/2.0);
boundingBox.setMouseTransparent(true);
root3D.getChildren().add(boundingBox);
}
Two things:
The table3D's boundsInParent is not updated properly when texts are added for the first time.
What would be the right way of putting texts on 3D nodes? I am having to manipulate a whole lot to bring the texts as required.
Sharing code here.
For the first question, about the "jump" that can be noticed just when after scrolling a new text item is laid out:
After digging into the code, I noticed that the UIPane3D has a VBox textPane that contains the different Text nodes. Every time updateContent is called, it tries to add a text node, but it checks that the vbox's height is always lower than the pane's height, or else the node will be removed:
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
While this is basically correct, when you add a node to the scene, you can't get textPane.getHeight() immediately, as it hasn't been laid out yet, and you have to wait until the next pulse. This is why the next time you scroll, the height is correct and the bounding box is well placed.
One way to force the layout and get the correct height of the textNode is by forcing css and a layout pass:
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
// force css and layout
textPane.applyCss();
textPane.layout();
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
Note that:
This method [applyCss] does not normally need to be invoked directly but may be used in conjunction with Parent.layout() to size a Node before the next pulse, or if the Scene is not in a Stage.
For the second question, about a different solution to add Text to 3D Shape.
Indeed, placing a (2D) text on top of a 3D shape is quite difficult, and requires complex maths (that are done quite nicely in the project, by the way).
There is an alternative avoiding the use of 2D nodes directly.
Precisely in a previous question, I "wrote" into an image, that later on I used as the material diffuse map of a 3D shape.
The built-in 3D Box places the same image into every face, so that wouldn't work. We can implement a 3D prism, or we can make use of the CuboidMesh node from the FXyz3D library.
Replacing the Box in UIPaneBoxGroup:
final CuboidMesh contentShape;
UIPane3D displaypane = null;
PhongMaterial shader = new PhongMaterial();
final Color pColor;
public UIPaneBoxGroup(final double pWidth, final double pHeight, final double pDepth, final Color pColor) {
contentShape = new CuboidMesh(pWidth, pHeight, pDepth);
this.pColor = pColor;
contentShape.setMaterial(shader);
getChildren().add(contentShape);
addInfoUIPane();
}
and adding the generateNet method:
private Image generateNet(String string) {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
Label label5 = new Label(string);
label5.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, 40));
GridPane.setHalignment(label5, HPos.CENTER);
grid.add(label5, 3, 1);
double w = contentShape.getWidth() * 10; // more resolution
double h = contentShape.getHeight() * 10;
double d = contentShape.getDepth() * 10;
final double W = 2 * d + 2 * w;
final double H = 2 * d + h;
ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(d * 100 / W);
ColumnConstraints col2 = new ColumnConstraints();
col2.setPercentWidth(w * 100 / W);
ColumnConstraints col3 = new ColumnConstraints();
col3.setPercentWidth(d * 100 / W);
ColumnConstraints col4 = new ColumnConstraints();
col4.setPercentWidth(w * 100 / W);
grid.getColumnConstraints().addAll(col1, col2, col3, col4);
RowConstraints row1 = new RowConstraints();
row1.setPercentHeight(d * 100 / H);
RowConstraints row2 = new RowConstraints();
row2.setPercentHeight(h * 100 / H);
RowConstraints row3 = new RowConstraints();
row3.setPercentHeight(d * 100 / H);
grid.getRowConstraints().addAll(row1, row2, row3);
grid.setPrefSize(W, H);
grid.setBackground(new Background(new BackgroundFill(pColor, CornerRadii.EMPTY, Insets.EMPTY)));
new Scene(grid);
return grid.snapshot(null, null);
}
Now all the 2D related code can be removed (including displaypane), and after a scrolling event get the image:
public void refreshBomUIPane() {
Image net = generateNet(displaypane.getText());
shader.setDiffuseMap(net);
}
where in UIPane3D:
public String getText() {
return infoTextKeys.stream().collect(Collectors.joining("\n"));
}
I've also removed the bounding box to get this picture:
I haven't played around with the number of text nodes that can be added to the VBox, the font size nor with an strategy to avoid generating images on every scroll: only when the text changes this should be done. So with the current approach is quite slow, but it can be improved notably as there are only three possible images for each box.
I am creating a game where circles fall from the top of the screen to the bottom. When the circle is clicked its suppose to re-spawn in a random position on top of the screen and with a random color. I am pretty sure my problem has to do with my line to determine if the mouse click was on one of the circles or not is working correctly. So my questions are how would I determine if a mouse click happened on one of the circles or on the background screen? and What is wrong with the following line? (Because I am almost certain that my problem is from that line)
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
My entire code is here:
public class ShapesWindow extends Application{
final int WIDTH = 640;
final int HEIGHT = WIDTH / 12 * 9;
Random r = new Random();
Circle circle;
double yCord;
long startNanoTime;
Group root = new Group();
Scene scene = new Scene(root, WIDTH, HEIGHT);
Canvas can = new Canvas(WIDTH,HEIGHT);
GraphicsContext gc = can.getGraphicsContext2D();
ArrayList<Shape> shape = new ArrayList<>();
#Override
public void start(Stage theStage) throws Exception {
theStage.setTitle("Click the bubbles!");
theStage.setScene(scene);
root.getChildren().add(can);
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
/* This adds 10 circles to my Group */
for(int i = 0; i < 10; i++){
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
circle = new Circle(15,randomColor());
root.getChildren().add(circle);
circle.setLayoutX(r.nextInt(WIDTH+15));
circle.setLayoutY(0);
shape.add(circle);
}
/* This my attempt at trying to handle the Mouse Events for each thing */
for(int i = 0; i < 10; i++){
shape.get(i).setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
shapeClicked(e);
}
});
}
startNanoTime = System.nanoTime();
new AnimationTimer(){
public void handle(long currentNanoTime){
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
yCord = t*20;
for(int i = 0; i < 10; i++){
/* This if statment allows nodes to wrap around from bottom to top */
if(yCord >=HEIGHT){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
resetNan();
}
shape.get(i).setLayoutY(yCord);
}
}
}.start();
theStage.show();
}
/*
* This Function is suppose the change the color and position of the circle that was clicked
*/
public void shapeClicked(MouseEvent e){
for(int i = 0; i < shape.size();i++){
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
}
}
/*
* This allows the value of startNanoTime to be indrectly change it can not be changed diretly
* inside of handle() inside of the Animation class
*/
public void resetNan(){
startNanoTime = System.nanoTime();
}
public Color randomColor(){
double R = r.nextDouble();
double G = r.nextDouble();
double B = r.nextDouble();
double opacity = .6;
Color color = new Color(R, G, B, opacity);
return color.brighter();
}
public static void main(String[] args){
launch(args);
}
}
Why not just
for(int i = 0; i < 10; i++){
Shape s = shape.get(i);
s.setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
s.setLayoutX(r.nextInt(WIDTH+15));
s.setLayoutY(0);
s.setFill(randomColor());
}
});
}
I know long time passed by, but if anyone else need to check if a mouse click event was on a Circle or any other shape it is better to use the built in .contains method. Thanks to Point2D from JavaFx geometry class you can check if a click (x,y coordinate) is on a shape or not, you don't have to worry about the click position: in the center or border.
for (Circle circle:listOfCircles) {
Point2D point2D = new Point2D(event.getX(),event.getY());
if (circle.contains(point2D)){
System.out.println("circle clicked");
}
}
Is it possible to clone javafx.scene.image.Image, not using pixel by pixel copying?
Or this is the only way?
The code below copied from your link and put into a separate function is definitely not the "only" solution to the problem. It is definitely the best work-around that i personally know of. So here is the code for cut&paste:
copyImage
/**
* copy the given image to a writeable image
* #param image
* #return a writeable image
*/
public static WritableImage copyImage(Image image) {
int height=(int)image.getHeight();
int width=(int)image.getWidth();
PixelReader pixelReader=image.getPixelReader();
WritableImage writableImage = new WritableImage(width,height);
PixelWriter pixelWriter = writableImage.getPixelWriter();
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
Color color = pixelReader.getColor(x, y);
pixelWriter.setColor(x, y, color);
}
}
return writableImage;
}
Use in the context of an ImageView
If you have an ImageView that might have a readonly image this is how you get a copy "on the fly".
Platform.runLater() might be needed when calling the setImage ...
/**
* get the writeAbleImage (if available)
*
* #return the writeAbleImage or null if the image is not writeAble
*/
public WritableImage getWriteableImage() {
if (image instanceof WritableImage) {
return (WritableImage) image;
} else {
LOGGER.log(Level.INFO,"image is not writeable will create a writeable copy");
WritableImage copyImage=copyImage(image);
image=copyImage;
imageView.setImage(image);
return copyImage;
}
}
Late answer, you probably made it so far.
My solution to this topic is like that.
// First create a cache image
planTiles[1][1] = new PlanTile(1, 1, "");
ImageView cache = planTiles[1][1].renderImage(); // timeconsuming operation
// In loop, use cache:
planTiles[iX][iY] = new PlanTile(iX, iY, "");
planTiles[iX][iY].setImage(cache.getImage());
This is the solution:
writableImage = SwingFXUtils.toFXImage(SwingFXUtils.fromFXImage(sourceImage, null), null)
Full code:
public class JavaFXApplication extends Application {
#Override
public void start(Stage primaryStage) {
Image sourceImage = new Image("http://goo.gl/kYEQl");
ImageView imageView = new ImageView();
imageView.setImage(sourceImage);
ImageView destImageView = new ImageView();
//copying sourceImage
destImageView.setImage(SwingFXUtils.toFXImage(SwingFXUtils.fromFXImage(sourceImage, null), null));
VBox vBox = new VBox();
vBox.getChildren().addAll(imageView, destImageView);
StackPane root = new StackPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 300, 300);
primaryStage.setTitle("java-buddy.blogspot.com");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I want to change the colors of background button and the color of text onfocus
How can I make it?
class RoundedRectField extends Field {
// Layout values
private static final int CURVE_X = 12; // X-axis inset of curve
private static final int CURVE_Y = 12; // Y-axis inset of curve
private static final int MARGIN = 2; // Space within component boundary
// Static colors
private static final int TEXT_COLOR = 0xFFFFFF; // White
private static final int BORDER_COLOR = 0xFF8000; // dark gray
private static final int BACKGROUND_COLOR = 0xFFFFFF; // White
private static final int TEXT_COLOR_selected = 0xFF6DB6;
private static final int BORDER_COLOR_selected = 0xFF8000;
private static final int BACKGROUND_COLOR_selected = 0xCCCCCC;
boolean _focus = false;
private static String text_button;
// Point types array for rounded rectangle. Each point type
// corresponds to one of the colors in the colors array. The
// space marks the division between points on the top half of
// the rectangle and those on the bottom.
private static final byte[] PATH_POINT_TYPES = {
Graphics.CURVEDPATH_END_POINT,
Graphics.CURVEDPATH_QUADRATIC_BEZIER_CONTROL_POINT,
Graphics.CURVEDPATH_END_POINT, Graphics.CURVEDPATH_END_POINT,
Graphics.CURVEDPATH_QUADRATIC_BEZIER_CONTROL_POINT,
Graphics.CURVEDPATH_END_POINT,
Graphics.CURVEDPATH_END_POINT,
Graphics.CURVEDPATH_QUADRATIC_BEZIER_CONTROL_POINT,
Graphics.CURVEDPATH_END_POINT, Graphics.CURVEDPATH_END_POINT,
Graphics.CURVEDPATH_QUADRATIC_BEZIER_CONTROL_POINT,
Graphics.CURVEDPATH_END_POINT, };
// Colors array for rounded rectangle gradient. Each color corresponds
// to one of the points in the point types array. Top light, bottom black.
private static final int[] PATH_GRADIENT = { 0xFF8000, 0xFF8000, 0xFF8000,
0xFF8000, 0xFF8000, 0xFF8000,
0xFC0500, 0xFC0500, 0xFC0500, 0xFC0500, 0xFC0500, 0xFC0500 };
// Center our readonly field in the space we're given.
public RoundedRectField(String text_button) {
super(FIELD_HCENTER | FIELD_VCENTER | READONLY);
this.text_button = text_button;
}
// This field in this demo has a fixed height.
public int getPreferredHeight() {
return 70;
}
// This field in this demo has a fixed width.
public int getPreferredWidth() {
return 240;
}
// When layout is requested, return our height and width.
protected void layout(int width, int height) {
setExtent(getPreferredWidth(), getPreferredHeight());
}
// When painting is requested, do it ourselves.
protected void paint(Graphics g) {
// Clear this area to white background, fully opaque.
g.clear();
g.setGlobalAlpha(255);
g.setBackgroundColor(BACKGROUND_COLOR);
// Drawing within our margin.
int width = getPreferredWidth() - (MARGIN * 2);
int height = getPreferredHeight() - (MARGIN * 2);
// Compute paths for the rounded rectangle. The 1st point (0) is on
// the left
// side, right where the curve in the top left corner starts. So the
// top left
// corner is point 1. These points correspond to our static arrays.
int[] xPts = { 0, 0, CURVE_X, width - CURVE_X, width, width, width,
width, width - CURVE_X, CURVE_X, 0, 0 };
int[] yPts = { CURVE_Y, 0, 0, 0, 0, CURVE_Y, height - CURVE_Y,
height, height, height, height, height - CURVE_Y };
// Draw the gradient fill.
g.drawShadedFilledPath(xPts, yPts, PATH_POINT_TYPES, PATH_GRADIENT,
null);
// Draw a rounded rectangle for the outline.
// I think that drawRoundRect looks better than drawPathOutline.
g.setColor(BORDER_COLOR);
g.drawRoundRect(0, 0, width, height, CURVE_X * 2, CURVE_Y * 2);
// Place some text in the center.
Font font = Font.getDefault().derive(Font.PLAIN, 9, Ui.UNITS_pt);
int textWidth = font.getAdvance(text_button);
int textHeight = font.getHeight();
g.setColor(TEXT_COLOR);
g.setFont(font);
g.drawText(text_button, (width / 2) - (textWidth / 2) - MARGIN,
(height / 2) - (textHeight / 2) - MARGIN);
}
protected void onFocus(int direction) {
_focus = true;
Dialog.alert("dcd");
invalidate();
super.onFocus(direction);
}
protected void onUnfocus() {
_focus = false;
invalidate();
super.onUnfocus();
}
}
You can do it several ways. One popular way is to provide custom focus drawing in the paint() method, which you already override.
You should be able to do this (I'm assuming you declared the _selected colors for the focused state):
if (isFocus()) {
g.setBackgroundColor(BACKGROUND_COLOR_selected);
else {
g.setBackgroundColor(BACKGROUND_COLOR);
}
...
if (isFocus()) {
g.setColor(TEXT_COLOR_selected);
} else {
g.setColor(TEXT_COLOR);
}
Those lines go in paint(), right where you are currently calling g.setBackgroundColor and g.setColor(TEXT_COLOR).
Then, you would override drawFocus() and do nothing, since your focus drawing is handled in paint():
protected void drawFocus(Graphics graphics, boolean on) {
// override superclass implementation and do nothing
}
Finally, you need to make your Field focusable, in order to ever receive focus. You can do so like this:
public RoundedRectField(String text_button) {
super(FIELD_HCENTER | FIELD_VCENTER | FOCUSABLE);
this.text_button = text_button;
}
If you need the field to be dynamically focusable (sometimes focusable, or sometimes not focusable), then you could implement this method:
public boolean isFocusable() {
But, if the field is always focusable, then using the FOCUSABLE flag in your constructor will work. I tested this out, and I saw the text color change with focus (on a OS 5.0 9550).
This is about as basic as it gets. I don't want to use an image file. Rather, I want to programmatically draw a circle and blit it to a surface (as they say in pygame).
I tried to follow the "Using CanvasLayers" example here:
https://developers.google.com/playn/devguide/rendering
From my game class:
// Surface
SurfaceLayer surface;
// Background
int width = 640;
int height = 480;
//ImageLayer bgLayer;
CanvasImage bgImage;
Canvas canvas;
// Circle
CanvasImage circleImage;
//ImageLayer circleLayer;
int circleRadius = 20;
int circleX = 0;
int circleY = 0;
#Override
public void init() {
// create a surface
surface = graphics().createSurfaceLayer(width, height);
graphics().rootLayer().add(surface);
// create a solid background
// http://code.google.com/p/playn101/source/browse/core/src/main/java/playn101/core/J.java#81
bgImage = graphics().createImage(width, height);
canvas = bgImage.canvas();
canvas.setFillColor(0xff87ceeb);
canvas.fillRect(0, 0, width, height);
//bgLayer = graphics().createImageLayer(bgImage);
//graphics().rootLayer().add(bgLayer);
// create a circle
circleImage = graphics().createImage(circleRadius, circleRadius);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circleX, circleY, circleRadius);
//circleLayer = graphics().createImageLayer(circleImage);
//graphics().rootLayer().add(circleLayer);
}
#Override
public void paint(float alpha) {
// the background automatically paints itself, so no need to do anything
// here!
surface.clear(0);
surface.drawImage(bgImage, 0, 0);
surface.drawImage(circleImage, 100, 100);
}
But I get a blank window in Java and Eclipse complains:
The method drawImage(CanvasImage, int, int) is undefined for the type
SurfaceLayer
That, however, is the way it is used in the example at the link.
If the code you provided even compiles, then you are using some very old version of PlayN.
Update to PlayN 1.1.1 and fix the compilation errors that result, and your code will work fine.
The following is your code updated to work with PlayN 1.1.1:
private SurfaceLayer surface;
private CanvasImage bgImage;
private CanvasImage circleImage;
#Override
public void init() {
// create a surface
int width = graphics().width(), height = graphics().height();
surface = graphics().createSurfaceLayer(width, height);
graphics().rootLayer().add(surface);
// create a solid background
bgImage = graphics().createImage(width, height);
Canvas canvas = bgImage.canvas();
canvas.setFillColor(0xff87ceeb);
canvas.fillRect(0, 0, width, height);
// create a circle
int circleRadius = 20;
int circleX = 0;
int circleY = 0;
circleImage = graphics().createImage(circleRadius, circleRadius);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circleX, circleY, circleRadius);
}
#Override
public void paint(float alpha) {
Surface s = surface.surface();
s.clear();
s.drawImage(bgImage, 0, 0);
s.drawImage(circleImage, 100, 100);
}
If you really intend to make a game using this approach, you should use ImmediateLayer not SurfaceLayer. ImmediateLayer will issue your drawImage, etc. calls directly against the framebuffer. SurfaceLayer will make an off-screen framebuffer and render everything into that and then copy that off-screen framebuffer to the main framebuffer every frame (in addition to the double buffering naturally performed by OpenGL, etc.), resulting in a needless copy of your entire screen.
Your code looks fine to me...
set the canvasTransform,
canvas.setTransform(1, 0, 0, 1, 0, 0);
and please for testing purposes make the bg bigger
graphics().createImage(circleRadius, circleRadius); (400,400)