Personal tools

Drawing API

From OpenLaszlo

This is a Drawing API proposed for LPS 3.0. It is intended to support charting and graphing. This proposal calls for the implementation of a subset of the WHAT-WG graphics API.

Contents

Requirements

  • It MUST be possible to implement the API in Flash 6.
  • It MUST be possible to implement the API on non-Flash runtimes.
  • It MUST be possible to implement charting and graphing components using these APIs.
  • It MUST be possible to implement procedural buttons and borders using these APIs.
  • The APIs SHOULD implement a subset of a spec, to the extent that this is possible.

Rationale

Implementing a standard takes more implementation time than simply trampolining to the Flash APIs. It has these advantages: - Evangelizing the Laszlo platform. Being standards-based has helped a lot with XPath, for example. - Learning curve. Ditto. - Ecosystem. It would be nice to be able to use JavaScript functions and libraries that are written for WHAT-WG, for instance. - Implementation. Some implementations of the Laszlo may be able to leverage work. For example, Mozilla is adding support for the WHAT-WG drawing API; this would allow a Laszlo implementation to leverage the Mozilla code base.

Specification

Laszlo will implement a subset of the WHAT-WG graphics API.

A new class DrawView extends LzView.

The following will be members of the DrawView class. They have the same semantics as in the WHAT-WG spec.

 var globalAlpha; // (default 1.0)
 var strokeStyle; // (default black)
 var fillStyle; // (default black)
 function createLinearGradient(x0, y0, x1, y1): CanvasGradient;
 function createRadialGradient(x0, y0, r0, x1, y1, r1): CanvasGradient;
 var lineWidth ; // (default 1)
 function beginPath();
 function closePath();
 function moveTo(x, y);
 function lineTo(x, y);
 function quadraticCurveTo(cpx, cpy, x, y);
 function fill();
 function stroke();
 function clip();

Differences from WHAT-WG

The WHAT-WG proposal uses the <canvas> tag to define an element that contains a graphics content. Laszlo uses <canvas> as the document root of the main application. It would be possible to use <canvas> with a different meaning at non-toplevel, but this was deemed too confusing.

In the WHAT-WG proposal, the <canvas> element implements a getContext() method which returns a graphics context, which implements the drawing methods. In this proposal, the corresponding element implements the drawing methods directly.

Unsupported APIs

Missing Features

These will not be supported, because they cannot be implemented in terms of the Flash drawing API:

 var globalCompositeOperation; // (default over)
 function createPattern(image, repetition);
 var lineCap: String; // "butt", "round", "square" (default "butt")
 var lineJoin: String; // "round", "bevel", "miter" (default "miter")
 var miterLimit; // (default 10)
 var shadowBlur; // (default 0)

High-level APIs

The following will not be implemented because of time limitations, but could be implemented using the Flash APIs:

 function clearRect(x, y, w, h);
 function fillRect(x, y, w, h);
 function strokeRect(x, y, w, h);
 function bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
 function arcTo(x1, y1, x2, y2, radius);
 function rect(x, y, w, h);
 function arc(x, y, radius, startAngle, endAngle, in boolean clockwise);

Transformation Stack

This is too much work to implement for LPS 3.0; but see Appendix A for an idea as to how.

 function save();
 function restore();
 function scale(x, y);
 function rotate(angle);
 function transform(x, y);

Shadows

These could be implemented by rendering every object twice.

 var shadowOffsetX; // (default 0)
 var shadowOffsetY; // (default 0)
 var shadowColor: string; // (default black)

Issues

In the WHAT-WG proposal, the <canvas> element implements a getContext() method which returns a graphics context, which implements the drawing methods. In this proposal, the view element implements the drawing methods directly. Java2D also uses a separate graphics context. The approach in this proposal is conceptually the same as using the view as a facade for an implicit graphics context which is not represented in the implementation. Should it implement getContext() instead?

What should the name of the view element be?

Design

The major difference between WHAT-WG and the Flash drawing model is that WHAT-WG path operations are accumulated and then stroked, while in the Flash drawing model they are stroked immediately. A smaller difference is that WHAT-WG represents the gradient as an object, while in the Flash API it's a sequence of parameters. The following pseudocde could be used to adapt WHAT-WG API calls into Flash API calls.

I'm using "function foo.bar ..." as pseudocode for "foo.prototype.bar = function ...".

   class CanvasGradient;
   
   function DrawView.createLinearGradient(x0, y0, x1, y1) {
     var gradient = new CanvasGradient;
     gradient.applyTo = function (mc) {
       mc.beginGradientFill('linear', ...);
     }
     return gradient;
   }
   
   function DrawView.createRadialGradient(x0, y0, r0, x1, y1,r1) {
     var gradient = new CanvasGradient;
     gradient.applyTo = function (mc) {
       mc.beginGradientFill('linear', ...);
     }
     return gradient;
   }
   
   var DrawView.fill
   var DrawView.__path;
   
   function DrawView.beginPath () {this.__path = []}
   
   function DrawView.moveTo(x, y) {
     this.__path.extend([MOVETO_OP, x, y]);
   }
   
   ...
   
   function DrawView.fill() {
     var mc = this.mc;
     if (typeof this.fillStyle == 'object' && this.fillStyle instanceof CanvasGradient) {
       this.fillStyle.__applyTo(mc);
     } else {
       mc.beginFill(this.fillStyle, this.globalAlpha);
     }
     this.__playPath();
     mc.endFill();
   }
   
   function DrawView.stroke() {
     this.lineStyle(this.lineWidth, this.lineStyle, this.globalAlpha);
     this.__playPath();
   }
   
   function DrawView.__playPath() {
     var p = this.__path;
     var mc = this.mc;
     for (var i = 0; i < p.length; i++) {
       var op = p[i];
       var optype = op[0];
       if (optype == MOVETO_OP) mc.moveTo(op[1], op[2]);
       else if (optype == LINETO_OP) ...
     }
   }

Issues

Katherine Aaker points out: I find I use the movieclip model often when using the Drawing API- Once you draw more than a few hundred points in a movieclip, the player tends to start chugging. If, for instance, I'm doing a program where the user can free-draw, I create a new movieclip every few hundred points, even if they haven't released the mouse. This is something you could probably do automatically and would be a nice performance boost.

Appendix A: Transforms Stack

Here's an implementation of the WHAT-WG transform stack, that fast-tracks the identity matrix. This is is not proposed for the initial implementation of the Laszlo drawing API, but is here in case anyone wants to play with it.

   var IDENTITY_TRANSFORM = 0;
   var SCALE_TRANSFORM = 1;
   var TRANSLATE_TRANSFORM = 2;
   var SCALE_TRANSLATE_TRANSFORM = 3;
   var AFFINE_TRANSFORM = 4;
   
   var identityDrawingOps = {
     moveTo: function (x, y) {this.mc.moveTo(x, y)}
     ...
   }
   var translateDrawingOps = {
     moveTo: function (x, y) {this.mc.moveTo(this.matrix[2][0]+x, this.matrix[2][1]+y)}
     ...
   }
   var scaleTranslateDrawingOps = {
     moveTo: function (x, y) {
       var m = this.matrix;
       this.mc.moveTo(m[0][0]*x+m[2][0], m[0][1]*x+m[2][1]);
     }
     ...
   }
   var affineTransformDrawingOps = {
     moveTo: function (x, y) {...}
     ...
   }
   var matrixStateFunctions = {
     IDENTITY_TRANSFORM: identityDrawingOps,
     ...
   }
   
   function DrawingView() {
     ...
     this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 0]];
     this.setMatrixState(IDENTITY);
   }
   
   DrawingView.prototype.setMatrixState = function (state) {
     this.state = state;
     var ops = matrixStateFunctions[state];
     this.moveTo = ops.moveTo;
     ...
   }
   
   DrawingView.prototype.promoteMatrixState = function (state) {
     if (state+this.state == SCALE_TRANSLATE_TRANSFORM) {
       this.setMatrixState(state+this.state);
     } else if (state > this.state) {
       this.setMatrixState(state);
     }
   }
   
   DrawingView.prototype.scale = function(x, y)  {
     ...
     this.promoteMatrixState(SCALE_TRANSFORM);
   }
   
   ...


Examples

LissajousCurves