clouds

Creation
  Pnglet
Drawing
  color
  point
  line
  polygon
  rectangle
  oval
  arc
  chord
  pieslice
  fill
  smoothPoint
  smoothLine
  smoothPolygon
Display
  output
Information
  width
  height
  depth
  getPoint
  red
  green
  blue
  alpha
Plans
Bugs
Source
Credits

Pnglets

Pnglets allow client side JavaScript to create graphics inside a web page. It only works with Netscape 4.06+, and it only works one image at a time, but it works. All the example images below, assuming they show up, are Pnglets.

This page demonstrates a method of drawing images using JavaScript, without JavaScript enabled you won't see any pictures.

There are three possible ways of inserting JavaScript generated graphic data into a web page, none of which work with Internet Explorer. Navigator 4.05 has problems executing the JavaScript (a local variable value is overwritten by a function call), and displaying the result (the images converted to plain text). This page is best viewed with some Netscape Navigators 4.06+, but it works with Netscape 7 so it may work on the many browsers based on Mozilla.

Creation

Pnglet(width, height, depth) - Pnglet creates a new Pnglet of specified width and height with depth colors.

Note that depth usually means the number of bits per pixel, but it doesn't mean that here.

// a 32 pixel, 8 color image.
var p = new Pnglet(32, 32, 8);

Drawing

color(red, green, blue, alpha) - color returns a pixel value for drawing with the specified red, green, blue, and alpha values. The values vary from 0 to 255. The alpha value may be omitted in which case it defaults to 255 or opaque. Absent any other arrangements, an empty image is filled with the first color allocated. The treatment of alpha by browsers is flawed.

// some opaque colors
var black =   p.color(  0,  0,  0);
var red =     p.color(255,  0,  0);
var yellow =  p.color(255,255,  0)
var green =   p.color(  0,255,  0);
var cyan =    p.color(  0,255,255);
var blue =    p.color(  0,  0,255);
var magenta = p.color(255,  0,255);
var white =   p.color(255,255,255);

point(pointColor, x, y, ...) - point draws a pixel of color pointColor at coordinate x, y. Coordinates are measured from the upper left corner of the image, x increasing rightward, y increasing downward. If additional point coordinates are supplied, they will also be drawn.

// set a red pixel
p.point(red,16,16);

// set several red pixels
p.point(red,16,16,8,16,16,8);

line(lineColor, x1, y1, x2, y2, ...) - line draws a line of color lineColor from coordinates x1, y1 to coordinates x2, y2. If additional coordinates are supplied then a polyline will be drawn.

// draw a green line
p.line(green, 2,2, 32,16);

// draw a green polyline
p.line(green, 2,2, 32,16, 16,32, 4,7);

polygon(outlineColor, fillColor, x, y, ...) - polygon draws a closed polygon with outline color outlineColor and fill color fillColor starting at x, y and continuing through all coordinates supplied. There must be at least three sets of coordinates supplied.

// draw a blue triangle outline
p.polygon(blue, null, 16,8, 8,16, 24,16);

// draw a filled red triangle
p.polygon(null, red, 16,8, 8,16, 24,16);

// outline and fill
p.polygon(blue, red, 16,8, 8,16, 24,16);

rectangle(outlineColor, fillColorx0, y0, x1, y1) - rectangle draws a rectangle with outline color outlineColor and fill color fillColor with the corners specified by the coordinates x0, y0 and x1, y1. The rectangle includes x0 and y0 but excludes x1 and y1.

// draw a white rectangle
p.rectangle(white, null, 2,2, 30,30);

// draw a filled red rectangle
p.rectangle(null, red, 2,2, 30,30);

// outline and fill
p.rectangle(white, red, 2,2, 30,30);

oval(outlineColor, fillColor, x, y, w, h) - oval draws an oval with outline color outlineColor and fill color fillColor centered at x, y, with width w and height h.

// draw a green outlined oval
p.oval(green, null, 16,16, 20,20);

// draw a blue filled oval
p.oval(null, blue, 16,16, 20,20);

// outline and fill
p.oval(green, blue, 16,16, 20,20);

arc(color, x, y, w, h, s, e) - arc draws an arc of an ellipse of color color centered at x, y, with width w and height h, from starting angle s with angular extent e. Angles are measured in degrees anticlockwise starting from 3 o'clock.

// draw a green arc
p.arc(green, 16,16, 20,20, 0,270);

chord(outlineColor, fillColor, x, y, w, h, s, e) - chord draws an chord of an oval with outline color outlineColor and fill color fillColor centered at x, y, with width w and height h, from starting angle s, and with angular extent e. Angles are measured in degrees anticlockwise starting from 3 o'clock.

// draw a green outlined chord
p.chord(green, null, 16,16, 20,20, 0,270);

// draw a blue filled chord
p.chord(null, blue, 16,16, 20,20, 0,270);

// outline and fill
p.chord(green, blue, 16,16, 20,20, 0,270);

pieslice(outlineColor, fillColor, x, y, w, h, s, e) - pieslice draws an pieslice shape with outline color outlineColor and fill color fillColor centered at x, y, with width w and height h, from starting angle s and with angular extent e. Angles are measured in degrees anticlockwise starting from 3 o'clock.

// draw a green outlined pie slice
p.pieslice(green, null, 16,16, 20,20, 0,270);

// draw a blue filled pie slice
p.pieslice(null, blue, 16,16, 20,20, 0,270);

// outline and fill
p.pieslice(green, blue, 16,16, 20,20, 0,270);

fill(outlineColor, fillColor, x, y) - fill fills a region with color fillColor starting from x, y and bounded by the color outlineColor. If outlineColor is null, then fill flood fills a region with color fillColor starting from x, y and covering the contiguous region with the color originally found at x, y.

// fill to a green border
p.oval(blue, null, 11,11, 10,10);
p.oval(green, null, 16,16, 15,15);
p.fill(green, blue, 16,16);

// fill the region at 16,16
p.oval(blue, null, 11,11, 10,10);
p.oval(green, null, 16,16, 15,15);
p.fill(null, blue, 16,16);

smoothPoint(smoothSteps, pointColor, x, y, ...) - smoothPoint draws pixels of color pointColor at smoothSteps smoothed coordinates for each consecutive pair of input coordinates. If the first and last coordinates match, then the smoothing will form a closed curve.

// draw smoothed red pixels
p.smoothPoint(5, red,16,16,8,16,16,8);

smoothLine(smoothSteps, lineColor, x1, y1, ...) - line draws a smoothed lines of color lineColor with smoothSteps points replacing each input line. If the first and last coordinates match, then the smoothed line will form a closed curve.

// draw a smoothed green polyline
p.smoothLine(5, green, 2,2, 32,16, 16,32, 4,7);

smoothPolygon(smoothSteps, outlineColor, fillColor, x, y, ...) - smoothPolygon draws a smoothed closed polygon with outline color outlineColor and fill color fillColor with smoothSteps points replacing each polygonal edge.

// draw a smoothed blue triangle outline
p.smoothPolygon(5, blue, null, 16,8, 8,16, 24,16);

// draw a filled red triangle
p.smoothPolygon(5, null, red, 16,8, 8,16, 24,16);

// outline and fill
p.smoothPolygon(5, blue, red, 16,8, 8,16, 24,16);

Display

output() - output converts the results of previous drawing commands into a PNG format image and returns the binary string representing that image. In Netscape, an image source URL of the form "javascript:p.output()" will display the image drawn in the Pnglet p.

// set an image source
document.image.foo.src = "javascript:p.output()";

Information

width - width is the Pnglet width.

// fetch the width
p.width;

height - height is the Pnglet height.

// fetch the height
p.height;

depth - depth is the number of colors in the Pnglet.

// fetch the depth
p.depth;

getPoint(x, y) - getPoint retrieves the color of the pixel at coordinate x, y.

// fetch a pixel
p.getPoint(0,0);

red(color) - red retrieves the red value from the color color.

// the pixel's red 
p.red(p.getPoint(0,0))

green(color) - green retrieves the green value from the color color.

// the pixel's green
p.green(p.getPoint(0,0))

blue(color) - blue retrieves the blue value from the color color.

// the pixel's blue
p.blue(p.getPoint(0,0))

alpha(color) - alpha retrieves the alpha value from the color color.

// the pixel's alpha
p.alpha(p.getPoint(0,0))

Plans

It would be easy to allow Pnglet's to appear anywhere that a color currently appears. This would allow arbitrary brush shapes to be used for points, lines, and outlines, and for arbitrary images to be used as fill patterns. This would require thinking out how to combine color tables with alpha channels, unless we switched to a full RGBA image format.

Bugs

Internet Explorer doesn't display javascript generated images. See the examples.js source for the variations attempted.

Netscape Navigator ignores the alpha values of colors, all are treated as opaque. If you start trying to figure out what you're supposed to do with arbitrary alpha values in a finite color map, you can almost sympathize with the slackers who punted on this. But, then again, they were getting paid for this and they also got a pile of Netscape/AOL stock. Boyd says they earned it even if they did punt the PNG's.

Netscape gets confused if the same "javascript:expression" URL returns different images. That is, if you write a simple animation:

var p = new Pnglet(64, 64, 8);
var black = p.color(0,0,0,0);
var white = p.color(255,255,255);
p.line(white, 0,0, 64,64);
document.images.myImage.src =
   "javascript:p.output()";
p.line(white, 64,64, 0,32);
document.images.myImage.src =
   "javascript:p.output()";
then it might happen that Netscape won't redisplay the image after the crucial second line segment is drawn, because it appears to be identical to the first URL. I think you can avoid this problem by simply assigning p.output() to a unique variable name or array element, but I haven't tried it.

Netscape gets confused if more than one "javascript:expression" is being computed at once, as when several <IMG SRC="javascript:..."> tags appear on the same page and Netscape dispatches them to load in parallel. This nearly killed me as I tried to debug this web page, because every time I added a new image or altered the layout something new would appear to break. For days I thought that I was tickling some class of obscure Netscape JavaScript bugs, but it turns out to be exactly one obscure Netscape JavaScript bug: that JavaScript is not safely reentrant. You can avoid this problem in several ways, none of which are any fun. This page sequences the image generation with setTimeout() because a daisychain through the onload handlers of the images went into lala-land when loading over the Internet.

Netscape gets confused if more than one computed image gets assigned to the same Image.src. It continues to display the original computed image even while saving the newer computed image through the right mouse button "Save Image As ..." menu item.

Netscape 4.05 has problems both in evaluating the javascript and in displaying Png format from javascript urls.

Computing Pnglets is hideously expensive largely because of the checksums which are required by the Png format and the Zlib compression library. A browser could reduce the cost of Pnglets by a factor of 40 to 70 by eliminating the checksums.

Source and Credits

Pnglet.js was written by and is Copyright © 1999 by Roger E Critchlow Jr, Santa Fe, New Mexico, USA, and licensed under the Academic Free License version 2.1.

Pnglets were directly inspired by Tom Boutell's gd library for drawing GIFs, and much of the Pnglet code started out as a direct translation from the C of gd into JavaScript.

But along the way, parts of Tk crept into the design, including all the code for smoothing, the oval drawing was replaced by code from Foley & van Dam, the polygon fill was replaced by Paul Heckbert's code from Graphics Gems, and any remaining bugs must certainly be mine.

Thanks to Alex Vincent for pointing the costs of code which contains strict javascript warning errors and helping me eliminate the problems.




Copyright © 1999,
Roger E Critchlow Jr
Last modified: Mon Jul 7 15:05:24 MDT 2003
Santa Fe, New Mexico, USA
elf.org