Canvas for Bck2Brwsr

Almost two weeks ago I met Jaroslav Tulach in Linz for our annual NetBeans Platform certified Training at the Johanes-Kepler-University in Linz. Every year we take some time to talk about projects and do some coding over a couple of beers, producing “Barcode” if you will. Last year we created a dynamic upload center for NetBeans Platform applications. This year Jarda showed me his new project Bck2Brwsr. The goal of the project is to bring Java back to the Browser. Java has lost a lot of ground in that area, and the advent of smartphones and tablets speeds up this trend, since it’s getting harder and harder to set up the environment for executing Java applications or applets.

In the meantime JavaScript is delivering on the WORA promise Java once started with. And while Java is great as a language especially for larger projects, for structuring and modularizing code in order to create maintainable projects, the platform has lost a lot of ground on the devices. Developers like the language, the superior IDE support as compared to JavaScript, the compile time checks, but for Browser based Projects, Java is no longer a choice as client technology.

The main idea of Bck2Brwsr is to change that and make it dead simple to execute Java Code in the Browser on any device. Since JavaScript is ubiquitous, it’s the obvious target language. GWT solves the problem by providing some APIs and a compiler that generates JavaScript. Bck2Brwsr is different in that it creates a JVM that is written in JavaScript and is capable of executing Java Bytecode directly. So when a Java runtime is available, the code could be executed by that, and benefit from the superior performance of the JVM. But if there’s no JVM, a minimal environment is automatically installed and runs the code in the Bck2Brwsr VM. And if you wonder how such a system will perform, I found Bck2Brwsr to be surprisingly fast already.

Another difference to GWT is, that there’s no standard API for widgets. The idea is, that you as a developer can very easily create your own APIs. For my current projects that would be excellent. I’ve created a GameEngine that is using JavaFX Canvas to render TileMaps and Sprites, and my plan was to create a JavaScript/ HTML5 Canvas version of that as well, so I can target any device. Over a couple of Austrian beers Jarda showed me how to implement such an API, and yesterday I found a little time to code so I gave it a try.

The final plan is to recreate the JavaFX Canvas API to render to HTML5 Canvas. But there are some differences, e.g. how the individual functions like arc and arcTo work. The bigger differences are additional features of JavaFX Canvas like the ability to use SVG paths. All of that should be no big problem, but require some work. So I decide to first implement a 1:1 Java version of the HTML5 Canvas API.

Time to head over to w3schools and have a look at Canvas. There are several subsections. The first one is ”Colors, Styles, and Shadows”. Here we have a couple of properties we can read or set on the GraphicsContect of the Canvas, like fillStyle, strokeStyle, etc.. So what we first need to do is create a representation of the Canvas Element and it’s GraphicsContext:


public class Canvas extends Element {

public Canvas(String id) {
 super(id);
 }

// code omitted

@JavaScriptBody(
 args = {"el"},
 body = "var e = window.document.getElementById(el.fld_id);\n"
 + "return e.getContext('2d');\n")
 private native static Object getContextImpl(Canvas el);

public GraphicsContext getContext() {
 return new GraphicsContext(getContextImpl(this));
 }

We will also need to register our Element with Bck2Brwsr’s PageProcessor. Now when the PageProcessor hits a Canvas Element on the Web page, it will create a Canvas Element for us. The @JavaScriptBody annotation can be used to directly generate the native JavaScript Code for us. In getContextImpl it will find the current Canvas Object and call a method on it to give us the GraphicsContext. We then create a Java Object Wrapper around that JavaScript Object:


public class GraphicsContext {

Object context;

GraphicsContext(Object contextImpl) {
 this.context = contextImpl;
}

@JavaScriptBody(args = {"style"}, body = "this.fld_context.fillStyle=style;")
 public native void setFillStyle(String style);

 @JavaScriptBody(args = {}, body = "return this.fld_context.fillStyle;")
 public native String getFillStyle();

// code omitted

}

So the getter/setter style for simple types is fairly simple to implement. It get’s slightly more complicated when dealing with Objects. HTML5 Canvas also allows us to set Gradients or Patterns as fills. These are JavaScript Objects, so we need some Wrappers to represent them in our Java Code, and we need to provide a reference to be used by the native code:


public void setFillStyle(LinearGradient style) {
 setFillStyleImpl(style.object());
 }

 public void setFillStyle(RadialGradient style) {
 setFillStyleImpl(style.object());
 }

 public void setFillStyle(Pattern style) {
 setFillStyleImpl(style.object());
 }

 @JavaScriptBody(args = {"obj"}, body = "this.fld_context.fillStyle=obj;")
 private native void setFillStyleImpl(Object obj);

The object() method just returns the wrapped JavaScript Object reference:


public class LinearGradient {

private final Object gradient;

LinearGradient(Object linearGradient) {
 this.gradient = linearGradient;
 }

Object object() {
 return gradient;
 }

 public void addColorStop(double position, String color) {
 addColorStopImpl(gradient, position, color);
 }

@JavaScriptBody(args = {"gradient", "position", "color"}, body =
 "gradient.addColorStop(position,color)")
 private native void addColorStopImpl(Object gradient, double position, String color);

}

The rest is applying this pattern to all API methods, so we get a simple 1:1 mapping to Java. And then we can use it in Java Code like this:


/**
 * Edit the index.xhtml file. Use 'id' to name certain HTML elements. Use this
 * class to define behavior of the elements.
 */
@Page(xhtml = "index.xhtml", className = "Index")
public class App {

@On(event = CLICK, id = "hello")
 static void hello() {
 GraphicsContext context = Index.MYCANVAS.getContext();
 // Paths
 context.beginPath();
 context.moveTo(100, 20);
 context.lineTo(200, 20);
 context.arcTo(300, 300, 50, 50, 40);
 context.setFillStyle("#ff0000");
 context.setShadowColor("#000000");
 context.setShadowOffsetX(10);
 context.setShadowOffsetY(10);
 context.setShadowBlur(20);
 context.fill();

 // TextMetrics
 context.fillText("width: " + context.measureText("Hallo").getWidth(), 10, 10);

 // Gradients
 LinearGradient grd = context.createLinearGradient(0, 0, 170, 0);
 grd.addColorStop(0, "black");
 grd.addColorStop(0.3, "magenta");
 grd.addColorStop(0.5, "blue");
 grd.addColorStop(0.6, "green");
 grd.addColorStop(0.8, "yellow");
 grd.addColorStop(1, "red");
 context.setFillStyle(grd);
 context.fillRect(10, 200, 200, 200);

 // Pixel Manipulation
 ImageData imageData = context.getImageData(100, 0, 100, 100);
 context.putImageData(imageData, 200, 200);

 // Image
 context.drawImage(Index.SCREAM, 1, 1, 100, 100);

 // Fonts
 context.setFont(
 "20px Georgia");
 context.strokeText("Hello World!", 500, 50);
 context.setFont(
 "30px Verdana");
 LinearGradient gradient = context.createLinearGradient(0, 0, 800, 0);
 gradient.addColorStop(0, "magenta");
 gradient.addColorStop(0.5, "blue");
 gradient.addColorStop(1.0, "red");
 context.setStrokeStyle(gradient);
 context.strokeText("Big smile!", 500, 90);
 }
}

The code doesn’t look to different to JavaFX Canvas, does it? So here’s the ugly result :-):

It’s really easy and straightforward to create an API like that, and I’m looking forward to do some more coding with Bck2Brwsr!

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>