DoodlePad Home

Quick links

Original DoodlePad (no longer works): DoodlePad-1996
DoodlePad emulator: DoodlePad-2019


My thanks to the person(s) in the Computer Science department at Sacramento State University who preserved the original DoodlePad page all these years here, as over the years I had lost track of my original copy.

About the original DoodlePad (1996)

DoodlePad was a simple JavaScript drawing app I wrote in February, 1996, as an example for a chapter I wrote about Object Oriented Programming for Que's Special Edition: Using JavaScript, published in May of that year. As far as I know, DoodlePad was the first pure-JavaScript drawing app, and likely the last until browser support for the SVG image format came along circa 2001. There were some server-based drawing apps in that period (likely using ImageMagick on the back end to generate images), and there surely would have been Java applet and Adobe Flash implementations, but developing a JavaScript drawing app at that time was nearly impossible. Nearly.

Consider the state of the art in February, 1996:

And our assets? If we only had a wheelbarrow, that would be something. The wheelbarrow, it turned out, had been provided a few weeks earlier by JavaScript pioneer Achille Hui, who found that XBM format images could be generated from JavaScript by using a javascript: URI in the <img src=> attribute. (Note that XBM is no longer supported by browsers, and javascript: URIs are no longer accepted in <img src=> attributes.)

XBM provides just one bit per pixel, thus only two colors in an image. XBM images were usually rendered as black on light gray; DoodlePad defaults to the inverse, but provides switching between display modes. The attraction of XBM was that it's a text format, easily generated from JavaScript. (In fact, it's valid C code, which evidently became a security concern.)

Beyond those, the only other problem to overcome was that JavaScript refused to render an XBM image from the same URI more than once, leading to the xframes "array" at line 209; on each click on the "canvas", the redraw() function (line 268) rendered the XBM image into the next array element, and then that element was referred to in the canvas frame (line 355). Also, I did have to take care in building the XBM strings, as at the time building long strings by appending to the end could be painfully slow, so each row (scan line) is built separately, and only if it's changed, then the rows are assembled in logarithmic-ish time.

Looking back at DoodlePad after 23 years, there are a few small flaws, but mostly I'm satisfied with what I managed to pull off. I'm not sure why I assigned methods to objects on each instantiation, rather than assigning them once to their prototypes, but it worked. The circle drawing code is not optimal; I later discovered more efficient algorithms that don't take a square root for each computed point, but at least mine also only computed an octant's worth of points and extrapolated the rest. The promised "wall of fame" never materialized; I was just too busy, and by later in 1996 had moved on to mainly Java back-end work. But JavaScript was a lot of fun in those Wild West days.

About the DoodlePad emulator (2019)

I recently developed a DoodlePad emulator so people can see how the original looked and behaved. If you've checked out DoodlePad-1996, you know that not only can it no longer draw, but the UI doesn't even show up, since that also depended on XBM images and other features that are no longer supported in modern browsers. Also, it seems tiny, the fixed layout having been designed at a time when the typical PC screen resolution was 800x600 pixels.

My goal for the emulator was to stick with the original code as much as possible, and for the main JavaScript code, I came pretty close to that, adding just one new method and one property to the original xbmImage "class". But the HTML was another matter.

I knew at the outset that XBM was no longer supported, so my first thought was just to generate PNG images instead, but that turned out to be easier said than done. There are several JavaScript libraries out there that could help with all or part of the task, but I really did not want to turn the DoodlePad emulator into a modern, modular JavaScript app; I wanted to keep it all in one .html document if I could. So I looked at generating PNG myself. The format is well-documented, and I figured with Uint8Array, TextDecoder, and btoa(), I could construct data: URIs and just return those from xbmImage.toString() in place of the XBM strings. Piece of cake.

Then I noticed the fine print in the PNG spec that said that IDAT (pixel data) chunks had to be compressed, specifically using the DEFLATE algorithm. I figured that would be too much code for my little single document emulator, and also more work than I really wanted to do, but I went and looked at the DEFLATE spec anyway. And there I discovered — eureka! — that a DEFLATE block header could specify no compression, and just be followed by the uncompressed data. Problem solved!

I implemented that, and ran into the first problem. It was not possible to use TextDecoder to produce a string from a Uint8Array that btoa() could encode (to base64). There might be a character encoding out there that would work, but I could not find one documented. So, OK, I have my own Java base64 encoding function, 21 LOC, that I quickly ported to JavaScript, and just encoded from the Uint8Array directly, and constructed a data: URI. I plugged that into a test document, aaaaand... Firefox showed a broken image icon. Chrome and Edge, too. I wrote a quick Go program to write out the image to a .png file, and Gimp said it was invalid. Hmm.

I pored over the code and the PNG spec, and everything seemed right, but then I saw it. Multi-byte integers in PNG format are big-endian, but in the embedded DEFLATE header, they're little-endian. So I fixed that, regenerated the data: URI, and tried it. Firefox showed no error. Chrome showed no error. Edge showed no error. Gimp showed no error. And none of them showed my image, either. It seems PNG really, really wants that pixel data compressed, whether or not it's important to you.

So, it was on to plan B, the HTML <canvas> element. A canvas can't be initialized from HTML the way images can be, so there'd need to be an extra "render" step to write pixel data to the canvas. The most efficient way to do that, if you're writing every pixel, is using the ImageData object. The xbmImage.render() code you see now in DoodlePad-2019, implemented in function canvasRender(), is what I initially came up with.

And it didn't work.

I banged my head against that one for a while, tried a number of variations of the code, and ultimately concluded that ctx.getImageData() and/or ctx.putImageData() must be broken, or one of those experimental APIs that isn't fully implemented. Though I knew it would be inefficient, I rewrote the code to use ctx.fillRect() to draw each pixel as a 1x1 rectangle.

And that didn't work, either.

At that point, it dawned on me that the problem must lie somewhere else entirely. I had been testing the canvas-based code in the otherwise original DoodlePad framework, with the main JavaScript code in the <head> section, and the <canvas> elements in frames within a <frameset>. I tried it with both the canvas and the code inside the body of the same document. That worked fine. A few more experiments confirmed that it is not possible to modify a canvas in a frame from JavaScript code in another frame or the document head. Framesets were out.

So that's how the DoodlePad emulator ended up in its present form, using tables for layout, and canvas for rendering images. I scaled up the images and added frameset-like chrome to give a better sense of how it looked back in the day. I think as an emulation it comes pretty close to the original effect.

Bill Dortch
June 15, 2019