Home
Reading
Searching
Subscribe
Sponsors
Statistics
Posting
Contact
Spam
Lists
Links
About
Hosting
Filtering
Features Download
Marketing
Archives
FAQ
Blog
 
Gmane
From: Eugene Lazutkin <eugene <at> lazutkin.com>
Subject: Draft: Unified 2D graphics subsystem for Dojo
Newsgroups: gmane.comp.web.dojo.devel
Date: Wednesday 31st May 2006 01:58:20 UTC (over 11 years ago)
Better late than never. ;-)

Unified 2D graphics subsystem for Dojo
======================================

A realistic graphics subsystem should implement 6 major components:
  * Graphics Context – a container for graphics, which implements 
drawing API.
  * Shapes – a set of basic geometric shapes. The most basic shape is a 
path. One important concept is a group of shapes.
  * Fill – a way to fill in a shape. The most basic fill is a solid 
color fill. A pattern is a more generic fill. Linear and radial 
gradients are useful too.
  * Stroke – a way to stroke a shape. The most basic stroke should 
accept a color and a line width. Fancier styles include a pattern-based 
stroke, a line cap style, and line join style.
  * Transformations – a way to transform shape’s geometry before actual

drawing.

Fill, stroke, and transformation are separated as concepts because they 
can be applied to different shapes, or reused in some other way. This is 
a common scenario for many drawings.

Advanced facilities include:
  * Image operations – image-specific interpretation, composition, 
transformations, filters, and so on.
  * Text operations. For example, generation of paths with a text 
string, selection of a font.
  * Clipping – using shapes to clip drawings.
  * Additional adornments like automatic shadows, and such.

Current state of affairs
========================

At this moment we have 3 vector-based APIs on the market: SVG, VML, 
Canvas. SVG is supported natively by FF1.5 and Opera9, VML is supported 
natively by IE, Canvas is supported natively by FF1.5, Opera9, and 
Safari2.0.

SVG and VML define a markup language, while Canvas is provided as a 
JavaScript API + a canvas tag. SVG and VML expose much more than a way 
to draw pretty pictures: created objects can be modified on the fly, 
events (like, onclick) can be attached to provide easy interactivity.

The idea is to define a generic API, which can be effectively 
implemented in a renderer in terms of native APIs. This API can be used 
by graphics widgets written in generic manner, which can be used in any 
supported environment transparently. Clearly such API is going to be 
simpler than underlying APIs. GCD functionality can be implemented 
directly by all renderers. More advanced functionality can be 
implemented using fallbacks.

Additional requirement is to come up with a markup, which can be used 
inline independently of used renderer.

Given the current situation following functionality can be implemented 
more or less uniformly across existing graphics APIs: graphics context 
(surface), shapes (path, rectangle, polyline, ellipse, arcs, bezier), 
fill (solid color, linear gradient, image pattern), stroke (color, line 
width, line caps, line joins), transformation (translate, rotate, 
scale), image primitive, state stack manipulations (save, restore). 
State includes current values of transformation, fill style, and stroke 
style.

Proposed API
============

Everything goes into a new package dojo.gfx (we can debate name later, 
e.g., it can be placed in the existing dojo.graphics).
By necessity the API is heavily influenced by Canvas API --- the most 
basic one.

Global package-level functions
------------------------------

dojo.gfx.createSurface(parentNode, width, height, [renderer])

It creates a new surface for a picture. It corresponds to  element 
of SVG,  element of VML, and  element of Canvas. Width and 
height are in pixels. Parent node specifies a parent for newly created 
surface (there are several ways to create an element, e.g., before or 
after given node --- we can address them later). Optional renderer is a 
text string, which identifies a renderer: “svg”, “vml”, or
“canvas”. It 
is used to specify a renderer in an environment, where more than one 
renderer is available. If it is unspecified, the preferred/default 
renderer is used. createSurface returns a surface object or null, if 
cannot create a surface.

dojo.gfx.attachSurface(node)

It works just like createSurface but instead of creating new surface it 
attaches to existing element.

Surface object
--------------

Note: this API follows (more or less) the Canvas API --- the most 
restrictive one.

Path
++++

surface.beginPath()

Starts the path, returns nothing.

surface.moveTo(x, y)

Moves the current point of the path starting a new subpath. Returns
nothing.

surface.LineTo(x, y)

Adds the point to the path connecting the last point of the path with 
new point using a straight line. Returns nothing.

surface.arcTo(x1, y1, x2, y2, radius)

Adds two points to the path. (x1, y1) is connected with the last point 
using a straight line, if they are different. (x2, y2) is connected with 
(x1, y1) with an arc. Returns nothing.

surface.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

Adds (x, y) connecting it to the last point with a cubic bezier curve. 
Returns nothing.

surface.closePath()

Closes the last subpath with a straight line. Returns nothing.

surface.endPath()

Ends the path and sets it as the current path. Returns a shape object, 
or null (for Canvas). A shape object can be used for later 
modifications, or event processing.

Path (convenient)
+++++++++++++++++

surface.createRect(x, y, width, height)

This is a logical equivalent of (pseudo code):

function surface.createRect(x, y, width, height) {
     this.moveTo(x, y);
     this.lineTo(x + width, y);
     this.lineTo(x + width, y + height);
     this.lineTo(x, y + height);
     this.lineTo(x, y);
}

surface.createEllipse(cx, cy, rx, [ry])

Just like createRect it creates an ellipse. If ry is not specified, it 
creates a circle at (cx, cy) with rx radius. Returns nothing.

surface.createPoly(points)

Just like createRect it creates a polyline/polygon object. Returns nothing.

Image
+++++

surface.createImage(url, dx, dy [x, y, width, height])

Creates an image object from URL. Its top-left corner is placed at (dx, 
dy). Optional x, y, width, and height are used to select a subimage.

Fills
+++++

surface.createLinearGradient(x1, y1, x2, y2)

Returns a gradient object from (x1, y1) to (x2, y2) , which can be used 
as a fill style.

surface.createPattern(url, repetition)

Returns a (tiled) image pattern object, which can be used as a fill 
style. Repetition can be one of "repeat", or "no-repeat".

surface.setFill(fill)

Sets the current fill style using a color or a fill object. Returns
nothing.

surface.fill()

Fills the current shape with the current fill. Returns nothing.

Strokes
+++++++

surface.createStroke(width, cap, join, miter)

Return a stroke object. Cap can be one of “butt”, “round”, or
“square”. 
Join can be one of “round”, “bevel”, or a numeric miter value.

surface.setStroke(stroke)

Sets the current stroke style using a color or a stroke object. Returns 
nothing.

surface.stroke()

Strokes the current path. Returns nothing.

Transformations
+++++++++++++++

surface.translate(dx, dy)

Applies a translation transformation to the current matrix. Effectively 
shifts all coordinates by (dx, dy). Returns nothing.

surface.scale(sx, sy)

Applies a scaling transformation to the current matrix. Effectively 
multiplies all x coordinates by sx, and all y coordinates by sy. Returns 
nothing.

surface.rotate(angle)

Applies a rotation transformation to the current matrix. Effectively 
rotates all coordinates around (0, 0) by “angle” radians.

State
+++++

surface.save()

Pushes the current state (transformation, fill style, and stroke style) 
on the state stack. Returns nothing.

surface.restore()

Pops the current state (transformation, fill style, and stroke style) 
from the state stack. Returns nothing.

Shape object
------------

See notes below.

Gradient object
---------------

addColorStop(offset, color)

Adds a color point with specified offset (0.0-1.0). Returns nothing.

Pattern object
--------------

No methods.

Stroke object
-------------

No methods.

Discussion
==========

Random notes
------------

The idea was to come up with an API to solve simple yet frequent 
problems like charting, simple mapping, and simple art. If somebody 
desires to use SVG-specific stuff (e.g., builtin animation), or 
VML-specific stuff (e.g., formulas) please use the existing mechanism to 
create different versions of your widget.

I decided to use the term "surface" for now. Mainly because "canvas" and 
"context" are overused already. BTW, Cairo (FF's rendering engine) uses 
this term.

Canvas restricted a lot of things --- that was the reason to use its API 
as a model. For example it dictated save/restore state pattern.

VML has a pitiful support for radial gradient, which differs a lot from 
SVG/Canvas --- I decided not to support it for now. VML doesn't support 
more than 2 color stops. In most cases it is enough. Another VML 
deficiency is its pattern support --- only two styles are allowed. VML 
doesn't support path clipping --- it is not there either.

Alpha and z-position support should be addressed at some point.

It makes sense to implement some frequently used convenient 
transformation methods: rect-to-rect transformation, scale around a 
specified point, show an object, show a point, center. All of them can 
be implemented in renderer-independent way using existing basic methods.

Text support is not there mainly because it is not implemented in 
Canvas. In any case it is a difficult topic. I suggest to solve it on 
HTML level in specific widgets. Anyway unless user wants to do a "word 
art" it rarely makes sense to use distorted or decorated text. The only 
exception I know of is a simple text rotated by pi/2 angle. This one is 
truly useful for legends/labels.

I expect that beginPath()/endPath() will be implemented using  in 
VML, and  in SVG.

It may be possible to implement a Flash-based renderer. I don't know for 
sure because my knowledge of Flash is limited.

DOM support
-----------

Both VML and SVG support true objects, which can be used for event 
processing and/or animation. For example, it is possible to process 
onclick, change a position, or modify the fill style. In order to 
support the event processing, we can return raw objects for that. If we 
want to support an animation of some kind, we have to return some proxy 
objects, which will normalize its API.

There are two ways to implement the Canvas renderer (surface):

1) Simple implementation: all shapes are stroked/filled immediately, no 
objects are returned from endPath(), which will be an empty method. I 
tried to make sure that the API supports this scenario.

2) More elaborate implementation: every time a proxy shape object is 
created, it is saved in an internal list, and returned. Proxy shape 
object will have all necessary geometry descriptions. If user modifies 
it using standard API, the whole list is re-rendered reflecting the 
change. Obviously either delayed regeneration is used, or a special 
surface method should be called, when user finished modifying shapes. It 
is theoretically possible to use the same list to analyze and 
reverse-project mouse position to proper shapes thus simulating mouse 
events.

Unified markup language
-----------------------

It can be used to define an art, e.g., logos, or decorative elements of 
charts. I didn't include it here because it will depend on the API, 
which is going to interpret it. There are two possibilities: we can use 
some simplified subset of SVG (possibly augmented), or we can use our 
own markup, which closely corresponds to the API and can be interpreted 
consequently. Both approaches have obvious positive and negative sides. 
I need your input.

That's all folks!
=================

Thoughts? Criticism? Improvements? Other ideas?

Thanks,

Eugene

PS: Yes, I implemented renderers like that before (in several commercial 
systems) but not in js.
 
CD: 3ms