a canvas implementation for DesktopX
Published on August 26, 2008 By Littleboy In DesktopX

Update:

This small update brings a proper shadow implementation with blurring, two new functions (textPath and textAlongPath) and a few bug fixes. textAlongPath is still somewhat buggy with multi-segment paths but otherwise works great (see the TextWave demo). I fixed a few problems with arcTo that wasn’t working as specified and made a few changes here and there. More importantly, I fixed a nasty crash bug that was sometimes happening on exit.

There is also two new utility functions available from script allowing you to suspend the canvas drawing updates. This allows you to do all the drawing necessary for a frame of animation and refresh the canvas at the end, instead of having it refresh at 30fps while your script is doing all the drawing operations.

With this new API, it was possible to fix all the flickering in the included examples! I’ve also added a few new example scripts. Check out the Polygon example, as well as all the text-related ones.

Download


 

Every major browser has support for it, Yahoo! Widgets has support for it, how come there isn't something for DesktopX?

Enter DXCanvas, a DesktopX drawing plugin that implements the Canvas spec (plus some DesktopX-specific additions).

How does it work

Canvas Controller

DXCanvas is a DesktopX drawing plugin, which means it takes over drawing for the object it is associated to.

In the configuration, you can set the initial size for the canvas. By default, the drawing surface size will be 300x150. You can also specify whether you want the surface to be transparent and show the other objects or windows under it, or opaque.

A canvas object is made available to scripting. To be able to draw on the canvas, you need to request a context. This is done by calling getContext(type) on the canvas object. The only supported type right now is "2d".

See the list of functions in the implementation section. Note that some are slightly different from the Canvas specification, or are completely new. This is to adjust to differences in what is possible in a browser and in DesktopX.

Canvas Controller Widget

I've made a little widget to make testing easier. It loads a list of scripts from a user-defined folder and allows you to switch between them. It support scripts written in both JScript and VBScript.

It basically associate the script to an object having the DXCanvas plugin as a capability.

Two functions are required for the scripts to work: Object_OnScriptEnter() and Object_OnScriptExit().

The canvas size is reset its default size of 300x150 when switching between scripts, so make sure to set it to the required size if needed by your script.

To add a new script to the list, create a new .js or .vbs file in a folder and select this folder in the widget preferences. A few test scripts are included in the Script folder.

It also support subfolders (only 1 folder deep), so you can organize your scripts in subfolders.

How to help

Download the Canvas Controller widget and start creating scripts!

What to look for

  • strange behavior: colors are not right, context is not saved or restored correctly, etc.
  • the output from a script is different in Firefox or Safari
  • crashes or memory leaks

What is broken/not working properly

  • Drawing still flicker (not as badly as before)
  • This is a DEBUG build, so it's going to be slower than normal

Links

Changelog

1.1 Build 287:

  • Added manual drawing mode with suspendDrawing/resumeDrawing methods
    (this should allow users to work around the flickering)
  • Fixed text position being off baseline when drawing on a path
  • Fixed a rare crash when drawing text on a path
  • Activated Pixman MMX&SSE2 fast paths
  • Updated Pango to fix font leak (rev 2746)
  • Updated Pixman (0.13.2)

1.1 Build 269:

  • Added textPath, textAlongPath (with stroke or fill option)
  • Added support for shadows
  • arcTo is incorrect when the three points are collinear
  • arcTo should draw a straight line from P0 to P1
  • restore() with an empty stack has no effect
  • Corrected order of data returned by getImageData (BRGA -> RGBA)
  • ImageData is now converted to non-premultiplied color
  • Preserve the current path when clipping
  • rgba colors were not converted to string properly (green had the wrong value)
  • Some composite operations are now handled correctly
  • Fixed Debug build crashing when expired
  • Fixed stack overflow crash on exit
  • Added check for font validity on assignment and fallback to default font (will raise an error in debug mode if font is incorrect)
  • Moved state management to its own class
  • Updated Cairo(1.8.4), Pixman(0.12.0), Pango(1.22.3), Glib(2.18.3) and libpng(1.2.33)

1.0 Build 225:

  • Fixed large canvas objects taking a lot of CPU
  • Log is always enabled

1.0 Build 217

  • Fixed drawing being incomplete in some cases
  • Added workaround for pink color drawing transparent. DesktopX always use pink to draw transparent surfaces, even in per-pixel mode. rgb(255, 0, 255) will be changed to rgb(255, 1, 255)

1.0 Build 214

  • Added per-object log files
  • Added more log info when creating a new context object

1.0 Build 211

  • Added support for % in rgb/rgba colors
  • Added "transparent" color (equivalent to "rgba(0,0,0,0)")
  • Added SVG colors "darkgrey", "darkslategrey", "dimgrey", "grey", "lightgray", "lightslategrey" and "slategrey"
  • Zero size canvas is allowed
  • Properly share canvas state (context style stack was not properly shared before)
  • DrawImage and DrawImageRegion check for negative width and height and adjust the coordinates
  • Only match full color strings
  • Accept rgb values outside [0;255] and clamp the value properly
  • Clamp alpha and use premultiplied color values when painting with alpha (still somewhat wrong)
  • Fixed leak and crash with CanvasImageData & CanvasPixelArray
  • Fixed crash with invalid parameters in createImageData and getImageData
  • Zero-length line gradients should paint nothing
  • Radial gradients with the same start and end circle should paint nothing
  • Image data now accepts negative width and height (you get the untransformed data for the resulting rectangle)
  • arc() with zero radius draws a line to the start point.
  • arcTo() has no effect if the context has no subpaths or if P0 = P1
  • arcTo() draws a straight line to P1 if P1 = P2 or if radius = 0
  • bezierCurveTo() has no effect if the context has no subpaths
  • lineTo() has no effect if the context has no subpaths
  • quadraticCurveTo() has no effect if the context has no subpaths
  • Updated cairo, pixman and pango libraries

1.0 Build 201

  • Added hsl/hsla color parsing
  • Added some logging (DXCanvas.log in object directory)
  • Fixed text stroking always drawing at position (0,0)
  • Fixed text baseline being off by the text height

1.0 Build 191

  • Added redraw calls buffering (less flickering)
  • Added debugMode attribute to canvas. In debug mode, passing invalid parameters will result in an error being raised instead of the invalid value being silently ignored.
  • No longer returns errors when passing invalid parameters to a lot of functions (as specified in Canvas specification)
  • CanvasPixelArray.XXX6(index, value) now takes an int instead of a char and will clamp that value to [0;255]

1.0 Build 180

  • Implemented toImage
  • Implemented createImageData, getImageData and putImageData
  • Added canvas support to createPattern, drawImage and drawImageRegion
  • Fixed createPattern, drawImage and drawImageRegion not working in VBScript
  • Fixed createPattern, drawImage and drawImageRegion not making a copy of the source image or canvas
  • Fixed drawImageRegion using wrong default values for dw and dh
  • Fixed drawImageRegion not creating a new path, resulting in image "corruption"

1.0 Build 168

  • Implemented font attribute setter/getter
  • Implemented fillText, strokeText and measureText (using Pango)
  • Implemented arcTo (using patch from Behdad Esfahbod)
  • Fixed image loading (SD_LOAD_IMAGE seems to be corrupting some images...)

1.0 Build 159

  • Implemented textAlign and textBaseline attributes setters/getters  (not used yet)
  • Implemented createPattern, loadImage, drawImage and drawImageRegion
  • Update to cairo 1.7.4 (better support for text)
  • Statically linked to cairo lib (no need for cairo.dll anymore)

1.0 Build 149

  •  First test version

Download

You can download a test version here. It only includes the Canvas Controller widget for now. Please do not use the DXCanvas plugin in your own objects and widgets yet. This version of the plugin will expire on the 10/01/2008.

Implementation

Under the hood, it's using Cairo, a 2D vector graphics library that also powers the Mozilla and Yahoo Widgets implementations. Right now, the Cairo library is dynamically loaded at runtime, but I hope to have it statically linked into the plugin for the final version.

Here is a list classes with their attributes and functions and the state of their implementation

Canvas

NameImplementedComment
width YES  
height YES  
debugMode YES Invalid input will raise an error instead of being ignored.
getContext(type) YES  
toImage(path) YES Saves to a PNG file (replaces toDataURL)
suspendDrawing() YES  
resumeDrawing() YES  

 

CanvasRenderingContext2D

NameImplementedComment
globalAlpha YES  
globalCompositeOperation YES  
strokeStyle YES  
fillStyle YES  
lineWidth YES  
lineCap YES  
lineJoin YES  
miterLimit YES  
shadowOffsetX YES Shadows are not implemented yet.
shadowOffsetY YES Shadows are not implemented yet.
shadowBlur YES Shadows are not implemented yet.
shadowColor YES Shadows are not implemented yet.
font YES  
textAlign YES  
textBaseLine YES Hanging and ideographic baselines are treated as alphabetic.
canvas YES  
save YES  
restore YES  
scale(x, y) YES  
rotate(angle) YES  
translate(x, y) YES  
transform(m11, m12, m21, m22, dx, dy) YES  
setTransform(m11, m12, m21, m22, dx, dy) YES  
createLinearGradient(x0, y0, x1, y1) YES  
createRadialGradient(x0, y0, r0, x1, y1, r1) YES  
createPattern(input, repeat) YES  
clearRect(x, y, w, h) YES  
fillRect(x, y, w, h) YES  
strokeRect(x, y, w, h) YES  
beginPath YES  
closePath YES  
moveTo(x, y) YES  
lineTo(x, y) YES  
quadraticCurveTo(cpx, cpy, x, y) YES  
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) YES  
arcTo(x1, y1, x2, y2, radius) YES  
rect(x, y, width, height) YES  
arc(x, y, radius, startAngle, endAngle, anticlockwise) YES  
fill() YES  
stroke() YES  
clip() YES  
isPointInPath(x, y) YES  
fillText(text, x, y) YES  
strokeText(text, x, y) YES  
measureText(text) YES  
textAlongPath(text, stroke) YES Adds (or draws) the specified text along the current path
pathText(text) YES Adds the strokes from the specified text to the current path
loadImage(path) YES  
drawImage(input, dx, dy, dw, dh) YES  
drawImageRegion(input, sx, sy, sw, sh, dx, dy, dz, dh) YES  
createImageData(sw, sh) YES  
getImageData(sx, sy, sw, sh) YES  image data uses the BGRA format
putImageData(imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) YES  

 

Gradient

NameImplementedComment
addColorStop(offset, color) YES  

 

ImageData

NameImplementedComment
width YES  
height YES  
data YES  

 

PixelArray

NameImplementedComment
length YES  
XXX5(index) YES  
XXX6(index, value) YES  

 

TextMetrics

NameImplementedComment
width YES  

 


Comments (Page 3)
6 Pages1 2 3 4 5  Last
on Sep 03, 2008

Your latest build fixed the problems I was having.  Thanks!!!!

on Sep 03, 2008

Littleboy,

The following code returns a Syntax error trying to run the script from the Controller.  The loadImage function won't take "C:\frame.png".  When I change it to "frame.png", the Syntax error is replaced by "Error running script" message: Error# -2147467259 There was an error loading image: frame.png"

frame.png is located at C:\ 

What am I doing wrong?

---------------------------------------------------------

Sub Load_and_Show
 Dim ctx
 Dim img

    set ctx = canvas.getContext("2d")
  
    img = ctx.loadImage("C:\frame.png")
    ctx.drawImage img, 100,100

End Sub

------------------------------------------------------

BTW - Thanks for helping me out, and thanks again for putting all of this together.

 

on Sep 03, 2008

loadImage returns an object reference, so you need to use Set. This should work:

Code: vbscript
  1. Sub Load_and_Show
  2.  Dim ctx
  3.  Dim img
  4.     set ctx = canvas.getContext("2d")
  5.   
  6.     set img = ctx.loadImage("C:\frame.png")
  7.     ctx.drawImage img, 100,100
  8. End Sub
on Sep 03, 2008

Thanks!  That did it.

Suppose I should read the Canvas Tutorial.

on Sep 03, 2008

This part is DX-specific

on Sep 04, 2008

Littleboy,

I've been experimenting with your plug-in on an XP machine (at work ) and after you removed the test code you mentioned, my problem with the greyed out drop down and missing tab in the properties disappeared.  Last night I downloaded the same build at home and tried it on my Vista machines (Home Premium) - running the latest build of DX and I'm experiencing the same greyed out dropdown and missing property tab.  I assume that this is a Vista specific problem.  Im not sure what OS you are building the plugin on, but I thought I would let you know what I'm experiencing. 

on Sep 04, 2008

Also..if it helps, I've seen the thread on Vista, SP1, and UAC.  I've had UAC turned off forever and never experienced any UAC/permission problems with DX before or after SP1.

on Sep 10, 2008

New (almost final) build. Flickering should be reduced a bit (I'm no longer asking for redraw every time an operation is done on the canvas). Error handling is also much closer to the canvas spec now (use debugMode if you want to get an error on invalid parameters).

I'll try to see if I can fix the damn flickering for the final version, but otherwise this build should be what will be uploaded on WinCustomize. Let me know if you find any showstopper bugs!

On my todo list for a future version:

  • Drawing text along a path
  • Shadows (lot of work )
  • Charts (doable via scripts, but given that it's a royal pain to share them...)

 

on Sep 10, 2008

Nice work on the latest build.  Much faster!

on Sep 10, 2008

Here are some shape scripts that I put together that might be useful to someone.  Some of the procedures already exist in the Canvas list, some don't, but these take away the need to worry about moving the pencil to x,y first and building paths with beginPath and Stroke'ing, (unless you need to build a complex path before drawing it).  Also, the procedures that use angles accept degrees, not radians.


'************************************************************************************
'Converts angles to radians
'************************************************************************************

Function inRadians(angle)
Dim PI
PI = 3.14159265

   inRadians = (angle * PI) / 180
End Function

'************************************************************************************
'Usage: showImage(context,locx,locy,path)
'************************************************************************************

Sub showImage(ctx,x,y,path)
  Dim img

    Set img = ctx.loadImage(path)
    ctx.drawImage img,x,y

End Sub

'************************************************************************************
'Usage: drawArc(context,locx,locy,radius, startangle - in degrees, endangle - in degrees, direction)
'  - direction = anticlockwise (true)  clockwise (false)
'************************************************************************************

Sub drawArc(ctx,x,y,radius,startAngle,endAngle,anticlockwise)
Dim startA
Dim endA

    startA = inRadians(startAngle)
    endA = inRadians(endAngle)

      ctx.beginPath

      ctx.moveTo x,y

      ctx.arc x,y,radius,startA,endA, anticlockwise
      ctx.stroke

End Sub

'************************************************************************************
'Usage: drawPieSlice(context,locx,locy,radius, startangle - in degrees, endangle - in degrees, direction, filled)
'  - direction = anticlockwise (true)  clockwise (false)
'************************************************************************************

Sub drawPieSlice(ctx,x,y,radius,startAngle,endAngle,anticlockwise,filled)
Dim startA
Dim endA

    startA = inRadians(startAngle)
    endA = inRadians(endAngle)

    ctx.beginPath
    ctx.moveTo x,y
    ctx.arc x,y,radius,startA,endA, anticlockwise
    ctx.lineTo x,y
    ctx.stroke
    if filled = true then
       ctx.fill
    end if

End Sub

'******************************************************************************
'Usage: drawCircle context,centerx,centery, radius, number of segments, filled
'******************************************************************************

Sub drawCircle(ctx,cx,cy,radius,num_segments, filled)
Const PI = 3.14159265

Dim X
Dim Y
Dim theta
Dim dtheta
Dim seg

    dtheta = 2 * PI/ num_segments
   
    ctx.beginPath
    ctx.moveto cx+radius, cy
   
    theta = 0
    For seg = 1 To num_segments
        theta = theta + dtheta
        ctx.lineto cx + (radius * Cos(theta)), cy + (radius * Sin(theta))
    Next
    if filled = true then
       ctx.fill
    end if
    ctx.stroke
End Sub

'******************************************************************************
'Usage: drawAngleLine context,startx,starty, length of line, angle in degrees
'******************************************************************************

Sub drawAngleLine(ctx,startx,starty,linelength,angle)
Const PI = 3.14159265
Dim theta
  
    theta = (2 * PI/ 360) * angle
    ctx.beginPath
    ctx.moveto startx,starty
    ctx.lineto startx + (linelength * Cos(theta)), starty + (linelength * Sin(theta))
    'drawLine ctx, startx + ((linelength-200) * Cos(theta)), starty + ((linelength-200) * Sin(theta)), startx + (linelength * Cos(theta)), starty + (linelength * Sin(theta))
   
    ctx.stroke
End Sub

'************************************************************************************
'usage : returns distance from point x1,x1 to point x2,y2
'************************************************************************************
Function distance(x1,y1,x2,y2)
  distance = sqr( ((x2-x1)*(x2-X1)) + ((y2-y1)*(y2-y1)))
End Function

'******************************************************************************
'Usage: drawRectangle context,startx,starty,endx,endy, filled
'******************************************************************************

Sub drawRectangle(ctx,x1,y1,x2,y2, filled)
 ctx.beginPath
 ctx.moveto x1, y1
 ctx.rect x1,y1,abs(x2-x1),abs(y2-y1)
 if filled = true then
           ctx.fill
        end if
 ctx.stroke
End Sub


'******************************************************************************
'Usage: drawGrid context,startx,starty,endx,endy, grid size
' - Draws grid by specifying the grid size in pixels
'******************************************************************************

Sub drawGrid(ctx,x1,y1,x2,y2,gridsize)
  Dim L 
  Dim Width
  Dim Height

        Width = ABS(x2-x1)
        Height = ABS(y2-y1)

        
        For L = 1 to (width+gridsize) step gridsize
          drawLine ctx,x1+L,y1,x1+L,y2
          drawLine ctx,x1,y1+L,x2,y1+L
        Next
 drawRectangle ctx,x1,y1,x2,y2,false
       
End Sub

'******************************************************************************
'Usage: drawGrid2 context,startx,starty,endx,endy, num of divisions along x, num of divisions along y
'  - Draws grid by specifying the number of divisions per direction
'******************************************************************************

Sub drawGrid2(ctx,x1,y1,x2,y2,xdivisions, ydivisions)
  Dim L 
  Dim Width
  Dim Height
  Dim xgridsize
  Dim ygridsize

        Width = ABS(x2-x1) 
        Height = ABS(y2-y1)
        xgridsize = round(Width/xdivisions)
        ygridsize = round(Height/ydivisions)
        
       
        For L = 1 to width step xgridsize
          drawLine ctx,x1+L,y1,x1+L,y2   
        Next

        For L = 1 to height step ygridsize
          drawLine ctx,x1,y1+L,x2,y1+L
        Next

 drawRectangle ctx,x1,y1,x2,y2,false
       
End Sub


'******************************************************************************
'Usage:  draws a line on the x-axis with ticks on it, could be used for graph axis
'******************************************************************************

Sub tick_x(ctx,x1,y1,x2,xdivisions,ticklength)
  Dim L 
  Dim Width
  Dim xgridsize
 
        Width = ABS(x2-x1) 
        xgridsize = round(Width/xdivisions)
     
        For L = 0 to width step xgridsize
          drawLine ctx,x1+L,y1,x1+L,y1-ticklength   
        Next
    
 drawLine ctx,x1,y1,x2,y1
       
End Sub


'******************************************************************************
'Usage:  draws a line on the y-axis with ticks on it, could be used for graph axis
'******************************************************************************

Sub tick_y(ctx,x1,y1,y2,ydivisions,ticklength)
  Dim L 
  Dim Height
  Dim ygridsize
         
        Height = ABS(y2-y1)
        
        ygridsize = round(Height/ydivisions)
        
       
        For L = 0 to height step ygridsize
         drawLine ctx,x1,y1+L,x1+ticklength,y1+L
        Next

 drawLine ctx,x1,y1,x1,y2
       
End Sub

'******************************************************************************
'Usage: drawEllipse context,centerx,centery, radiuswide, raiustall, number of segments, filled
'******************************************************************************

Sub drawEllipse(ctx,cx,cy,radius1,radius2,num_segments, filled)
Const PI = 3.14159265

Dim startX
Dim startY
Dim theta
Dim dtheta
Dim seg

    dtheta = 2 * PI/ num_segments
   
    ctx.beginPath
    ctx.moveto cx+radius1, cy
   
    theta = 0
    For seg = 1 To num_segments
        theta = theta + dtheta
        ctx.lineto cx + ((radius1) * Cos(theta)), cy + (radius2 * Sin(theta))
 if seg =1 then
        startX=cx + (radius1 * Cos(theta))
 startY=cy + (radius2 * Sin(theta))
        end if
    Next
    ctx.lineto startX, StartY
    if filled = true then
           ctx.fill
        end if
   
    ctx.stroke

End Sub


'************************************************************************************
'Usage: drawRoundRectangle context,Leftx,Lefty, width, height, corner radius, filled
'************************************************************************************

Sub drawRoundRectangle(ctx, x, y, width, height, radius, filled)
    ctx.beginPath
    ctx.moveTo x, y + radius
    ctx.lineTo x, y + height - radius
    ctx.quadraticCurveTo x , y + height, x + radius, y + height
    ctx.lineTo x + width - radius, y + height
    ctx.quadraticCurveTo x + width, y + height, x + width, y + height - radius
    ctx.lineTo x + width, y + radius
    ctx.quadraticCurveTo x + width, y, x + width - radius, y
    ctx.lineTo x + radius, y
    ctx.quadraticCurveTo x, y, x, y + radius
    if filled = true then
       ctx.fill
    end if
    ctx.stroke
End Sub

 

on Sep 11, 2008

Littleboy,

I'm still having problems with the new build running the Canvas Controller on my Vista machines (see reply #36 of this thread).  The controller and canvas procedures/plug-ins work great on my XP machine here at work. 

Any ideas?

Thanks.

on Sep 11, 2008

I haven't had time to look further into that problem yet. I'm using Vista myself and it works fine here. I included a dxpack in the latest build in case you want to take a look. Good work on the scripts. Are you working on a widget already?

I'm going to publish another test build soon as there is a problem with stroked text in the current public build (fixed internally).

on Sep 11, 2008

I have not started on a widget/object yet.  I tried to on Sep 4th when I posted about my problem with the Controller on Vista.  After I realized that the Controller would not work, I tried to make an object from scratch, added your plug-in under additional capabilities and tried to run some of the scripts I put together.  I could not go further from that point on because I got script errors  (Null), "Object not available" type errors and assumed that I either had a Vista problem or was not making a proper declaration somewhere in my script to include the Canvas plug-in.  I can examine the scripts in the Dxpack that you included in the build to see what I'm doing wrong.  I hope I'm just having a problem with your Canvas Controller widget and not the plug-in itself.  I REALY want this plug-in to work and have some project ideas in mind.

Some ideas include:

  - Charting and Graphing

  - Meters that can't be done with the current plug-ins:

      - for example, I just made a DX Wi-Fi meter that works on Vista.  It does not use any plug-ins, but uses different bitmaps for different states(meter levels).  I want the meter to be dynamically drawn so I'm not limited to discreet states (i.e. 5 states and 5 images)

  - DX based image editing

  - Live object reflections

     - I have not thought much about this yet, but is it possible to pass the .Image value from a normal DX object into the context of an object using the DXcanvas plugin so it can be manipulated or annotated with the drawing subs?

  - Realtime enhancements to objects, like running indicators, or annotations to highlight a specific area on an object

  - a simple implemantation of the LOGO language(which is fun for kids to play with).  I always liked moving the turtle around.

on Sep 11, 2008

I'm going to work on creating or more accurately adapting the Bar Chart Script (that I mentioned in the forum a while back) into a function. The basic function should be easy, the only issues I've experienced have been parsing csv files because of essentially no standards.

Wouldn't the live object reflection be possible already? Set up a mask object then draw a gradient with one color being transparent and import the image into that object.

Also, I would think you could easily get another object's picture by using Object.Picture? Off to try some of your ideas.

on Sep 11, 2008

Object.Picture I meant, not Object.Image

6 Pages1 2 3 4 5  Last