Canvas

Canvas is a program that makes a window with a white background
   and then reads draw-commands from a local socket (/tmp/canvas.sock),
   and executes them.

Canvas comes with a program called 'canvassock',
   which reads it's commandline argument
   and sends it as a message onto socket /tmp/canvas.sock .

Canvas is usefull for when you want to make or change a drawing or picture
   because canvas takes care of all interactions with X window system ;
   all you need to do is tell it what to draw.

Canvas is very suitable for use by a script or program,
   where the script computes what to draw, and issues draw commands,
   and canvas draws the image.

Features

Canvas uses "anti-aliased" drawing, client-side font rendering,
   and an externally accessible image buffer,
   and can read and write jpg and png, can report events, and can measure areas.

Anti-aliased drawing means that
   when a slanted line is drawn, that line would cover some pixels partly,
   and these pixels are given a color that is between line color and background color,
   depending on which amount of pixel is covered by line that is drawn.

Client-side font rendering means that
   when drawing text, canvas reads font-files, and fills in pixels as appropriate,
   and does not ask X-server to do that.
Canvas can not read any usual font file formats, but uses a custom format,
   which has better font metadata,
   and makes it possible to see whether a font has a glyph for a particular charactercode
   (which is not possible via X, it would give a box-like default glyph in that case).
This format contains glyphs as on-the-fly decompressable pixel maps,
   so it uses a bit less memory than X's bitmapped fonts
   but for larger fonts, uncompressed filesize is larger than that of X's vector fonts.
This part of Canvas is not really mature yet ;
   Current version does not include program to generate this type of fontfile
   and comes with only 8 fonts.

Externally accessible image buffer means that
   canvas internally uses an image file format (called 'imo')
   that consists of a small header (called 'imoheader')
   followed by an array of pixel-color-data,
   that is 1 byte opacity, 1 byte red, 1 byte green, 1 byte blue, (called 'orgb').
Memory used to store (and operate on) this imo file
   is mmapped shared as file /tmp/canvasbuf.imo
   (which is created when canvas starts, and is deleted at normal termination).
Thus it is possible for external programs to
   also open this file mmappped shared, and alter it's contents
   which results in contents that canvas sees to also be changed ;
This way canvas does not need to support any plugins.

Read and write jpg and png files means that
   canvas supports commands to read and write .imo files,
   and comes with utility programs that can convert .imo files
   to .png and .jpg formats and vice versa.

Report events means that
   you can tell it to report three kinds of events : click, doubleclick, and drag.
It will then send a reply containing a description of each event,
   such as "+click 1 123 45\ndrag 1 123 45 123 67".

Measure an area means that
   when you tell it to fill an area, it reports how many pixels it filled,
   and you can use command 'countarea' to do same thing but not paint any pixels.

How it works

Main components of canvas are
   * a socket to read commands and send replies ;
     it is present in filesystem as /tmp/canvas.sock ,
   * 'drawwidth' variable, which can be set with command 'setdrawwidth'
     and determines width of lines that are drawn.
   * 'layer', which is what lines and other shapes are drawn on ;
     it has a 4x4 array of subpixels per pixel, and has no color,
   * 'drawcolor' variable, which is an orgb,
     and can be set with command 'setdrawcolor',
     and takes effect when command 'flushlayer' is given.
   * 'imobuf', which is main image buffer and has imo file format ;
     Contents of layer are added to it (using drawcolor)
     when command 'flushlayer' is given.
   * an XImage, which receives content of imobuf in a format that X can use,
     and is sent to X to be displayed when command 'showimobuf' is given.

Example

As an example, here are commands to issue to draw a thick diagonal red line :
On shell commandline, start canvas by giving command "canvas" ;
   this will create a window with a white background.
Then send drawcommands to canvas,
   by using canvassock, or by using a programming language that supports sockets.
In this example i use canvassock :
On a shell commandline type
   canvassock "setdrawwidth 5"
   canvassock "drawline 10 10 100 100"
   canvassock "setdrawcolor 255 255 0 0"
   canvassock "flushlayer"
   canvassock "showimobuf"
To save result as an image file, also issue command
   canvassock "savearea 0 0 120 120 /tmp/thickreddiagonalline.imo"
And to convert it to jpeg, on shell commandline type
   ImoToJpeg /tmp/thickreddiagonalline.imo /tmp/thickreddiagonalline.jpg

This is the result :

Size of imo file is nearly 60 kB, size of jpg file is nearly 2 kB.

Note that x-position counts from left to right,
   while y-position counts from top to bottom.
Topleft pixel has coordinates (0;0).

Concatenating commands

If commands of example are issued by a script, they can be improved a bit :
Issuing a command through socket causes a delay,
   because script must be suspended so that canvas can be run to process command ;
This can be minimized by concatenating commands,
   adding a single newline character between commands to separate them.
When using canvassock, putting "\n" in string
   will be interpreted by shell as two characters '\' and 'n',
   but canvassock replaces this by a newline character.
   Canvassock also replaces character ';' by a newline character.

When executing a concatenated string of commands :
* If any command fails to execute properly,
   canvas sends '-' reply
   and does not try to execute any further commands from that string.
* If a command is supposed to produce a result (such as 'reportevents' or 'countarea')
   it must be last command,
   because any previous results are overwritten by execution of last command.

Utilities

Canvas program comes with some usefull utilities :
* ImoToJpeg : Canvas can store (part of) it's image in a file,
   but (currently) can only do that in imo file format ;
   ImoToJpeg operates on an imo file, and creates a jpeg file with same contents.
* JpegToImo : Canvas can load content of an imo file into it's imobuf ;
   using JpegToImo, you can thus also use jpeg files
   (other image processing programs do not know imo file format, but do know jpeg).
* ImoToPng : is similar to ImoToJpeg ;
   Compression is better than jpeg if image is
     a linedrawing that consists mainly of horizontal and vertical lines
     (so there are few intermediate colors present).
* PngToImo : does reverse of ImoToPng.
* canvassock : reads commands from it's stdin,
   sends them to socket /tmp/canvas.sock ,
   and prints reply on it's stdout
     (for all commands, reply '+' is sent after successfull completion,
     while '-' is sent if something was not right ;
     some commands return much longer replies, mainly for event-reporting) ;
   Purposes of canvassock are : to support programs that can not use sockets
   and to enable issuing commands from commandline.

Some Examples

Comparison of parabolic trough mirror versus cylindrical mirror,
   for case where focus length equals mirror width.

First a parabola was drawn in black, and rays of sunlight were drawn in green ;
   these all converge on focal line, height of which is indicated by grey line.
Then a circular arc was drawn in red, and rays of sunlight were drawn in yellow.
Below this is a plot of difference in y-coordinate between parabola and circle
   magnified 100 times.

Vectorfield of electrical force near two opposite electrical charges.

Lengths of linesegs indicate strengths of field at those locations ;
   directions of linesegs indicate directions of forces.

Energy density of sunlight at top of atmosphere as function of frequency
   in Watt per Hertz, computed from Planck's law.

Dark red part indicates infrared radiation, and purplish part is ultraviolet.

Invocation and errormessages

Canvas executable would normally reside in /usr/bin/canvas .
Syntax of invocation of canvas is :
   canvas (-v)* -wWIDTH -hHEIGHT -sSOCKETFILE -iIMOFILE
   where -w and -h specify sizes of canvas (in pixels) (default 760x570),
   -s specifies filename to use for socket (default /tmp/canvas.sock),
   -i specifies file to read for initial content of canvas (default none)
     which if specified must be an imo file,
   and every -v increases verbosity.

Canvas prints any errormessages and warnings to it's stdout ;
When invoked with a lot of -v's (more than 4 have no additional effect)
   this includes a lot of low-level detail that i needed for debugging at some time.

Socket

Canvas source contains example programs showing how to use a local socket
   from a perl script and from a C program.

For perl, this consist of 'use Socket' ;
   you can click here to see example code.

For C, this consists of the usual socket code,
   with one strange thing :
When client closes connection, canvas gets no notification of that
   (read returns no bytes, but there is no EPIPE),
   therefore canvas closes connection whenever it reads zero bytes,
   so your program should be prepared to handle a spurious disconnect
   (though this hasn't happened to me yet).
You can click here to see C example code.

After your script sends a message,
   OS needs to do a task-switch, to run canvas's command-interpreter.
This causes significant delay,
   so it is best to concatenate as many messages as possible
   (but note that canvas's readbuffer is only 1 MB).
Certainly after each 'showimobuf' command, messages should be sent,
   and if your script has any time that it idles,
   it would often be better to let canvas update image as far as possible.

When canvas successfully processes and executes your script's commands
   it sends a reply over socket, and that reply is "+" in most cases.
If some error occurred, reply is "-" instead.
Exception is command to report events :
   reply to these commands also start with either a + or a - ,
   but, if there were no errors and there are events to report,
   eventmessages are appended to that.
See furtheron for a more detailed description.

Animations

Since commands that canvas gets usually are given by a program
   and contents of imobuf are transferred to screen only when
   canvas is explicitly commanded to do so,
   it is easy to make animations with canvas.
These animations are not particularly fast,
   more precisely, they are rather slow.
Main reasons for this are that
* canvas does not use accelerated drawing
   that X driver for your videocard may offer
   because it needs it's imobuf to have contents of picture.
* whenever screen image is to change,
   a taskswitch is needed from your script to canvas, to handle commands you gave,
   then X part of canvas copies image to server,
   and another taskswitch is needed from canvas to X, to display result,
   and another taskswitch from X to canvas, so it can send reply,
   and another taskswitch from canvas back to your script.
Result is that on 500 MHz pentium 3 with ati mach64 videocard at 16 bits per pixel,
   for a 300x500 image,
   using fastest possible way to command screen updates,
   i can get 106 frames per second.

'Fastest possible way to command screen updates' here means that
   all images are precomputed and stored in separate XImages ;
This is a good idea for cyclical animations such as KDE's toothwheels,
   but not really a realistic estimate of general animation speed.
So i also made a script that generates a simple 300x600 pixels rotating toothwheel
   and that achieves 18 frames per second.

Drawing text

Canvas uses client-side font rendering.
It does this by using custom fontfiles,
   which have a format that i invented (filename extension .gly) .
This fileformat is described here.
Reasons for client-side rendering are that
* this is how X should have done it, in my opinion,
* client-side fonts enable measuring font properties such as boldness,
   and computing best inter-glyph distance, and more such things,
* i have a project that creates these fontfiles, so it was easy to use,
* if canvas would let X handle texts,
   then it would need to get image back from X server after every draw
   because it must keep imobuf content up to date,
* X does not define any reasonable fontfile format
   (.bdf is bloated, and uses more memory when loaded,
   whereas .gly is compact and uses on-the-fly decompressible glyphs)
A distinct disadvantage of this fileformat is that
   it is less optimal for variable-size ("vector") fonts
   because it would need a fontfile for every fontsize.

Canvas currently comes with only 8 fontfiles :
   AdobeCourier22re100
   AdobeHelvetica35r100
   AdobeSchoolbook20r100
   B&hLucida23r100
   DecTerminal15re75
   JmkNeepAlt20bre75
   JmkNeepAlt20re75
   MiscFixed20re75
Names indicate which font it contains,
   eg JmkNeepAlt20bre75 contains James M Knoble's "Neep" font,
   with alternative "&" character, 20 pixels high, regular (ie non-italic),
   bold, with english character set (ie ISO8859-1),
   theoretically it is optimized for 75 dots per inch screens.
I create these fontfiles by grabbing fonts that X provides,
   so adding more fonts is not much of a problem,
   but i haven't needed more than this yet, so this is the state of the canvas.
Canvas is not intended to be a text-drawing application,
   but it is nice to be able to add texts to images,
   and canvas does intend to make it easy to replace ascii-art.

An example where a text is used :


Events

Canvas can be used to make simple interactive applications
   and for this it has ability to report click, doubleclick, and drag events.
A click is valid if
   button is released not more than 0.2 seconds after it was pressed,
   and release position differs not more than 1 pixel from press location.
A doubleclick is valid if
   second click is not more than 0.3 seconds after first one,
   and release position differs not more than 1 pixel from press location.
A drag is valid if
   it is not a valid click
   and mouse did not leave window between press and release.
Events are reported over socket by appending them to standard "+" reply,
   and if more than one event is reported, they are separated by newline characters.
Format of report is "click %d %d %d", "dclick %d %d %d", "drag %d %d %d %d %d",
   where each %d represents a decimal integer number
   of which first one indicates button number (1 is usually left button),
   and subsequent ones indicate an x or y coord of a position,
   and of each position x is reported before y.

Screenshot of a simple interactive application :



Dragging from a symbol in top bar onto drawing copies symbol shape to that location ;
   this way simple electronic schematic drawings can be made.
Part of script that fills top bar is a separate file,
   so it is easy to make any kind of block-symbol drawing program.
This application is not part of canvas distribution ; it is just an example.

List of commands

This lists all commands that canvas accepts.
Commands are described with their syntax,
   in which $x, $y, etc. represent decimal floating point numbers.

Drawing shapes

Note that specifying a point that is outside window is not an error,
   and canvas will draw part of shape that is in window,
   but if all of shape to be drawn is outside window,
   it will issue a warning and reply with "-".

setdrawwidth $w
Sets value of drawwidth variable, which determines width of lines that are drawn.
$w is width measured in pixels. (pixels are assumed to be square).

setdrawcolor $o $r $g $b
Sets value of drawcolor variable,
   which determines color to use for copying content of layer to imobuf.

drawpoint $x $y
Conceptually draws a disc with radius 0.55 pixels,
   which, due to layer having 4x4 subpixels, is equivalent to
   setting all subpixels of a pixel, if $x and $y are integer.

drawlineseg $x1 $y1 $x2 $y2
Draws a line from (x1;y1) to (x2;y2).
Perpendicular to direction of line, width is determined by 'drawwidth' variable.
In direction of line, line does not extend beyond given endpoints.
Thus this command draws a rectangular block,
   and when a rectangular block must be drawn, this is fastest way to do it.
Curved lines are normally drawn using
   a sequence of drawlineseg and drawdisc commands.

drawdisc $x $y $r
Draws a disc with radius r and center at (x;y).
If x and y are integer and radius is circa 0.55, is equivalent to setting a pixel.

drawline $x1 $y1 $x2 $y2
Is same as
   drawdisc $x1 $y1 $drawwidth*0.5
   drawlineseg $x1 $y1 $x2 $y2
   drawdisc $x2 $y2 $drawwidth*0.5

drawcircle $x $y $r
Draws a circle with radius r and center at point (x;y),
   using value of 'drawwidth' variable as width of line.
   (so outer bound of this circle has radius $r + $drawwidth*0.5 ).

drawarc $x $y $r $a1 $a2
Is similar to drawcircle, but only draws part of it, from angle a1 to angle a2,
   where angles are expressed in whole turns (so 1.0 is equivalent to 360 degrees),
   are relative to 12 o'clock position, and increase clockwise.
Drawarc starts at angle of which fractional part equals that of a1,
   then draws clockwise,
   continuing until it arrives at angle of which fractional part equals that of a2.

Erasing shapes

For every shape-drawing command shown above,
   there is also a command to erase such a shape.
Layer does not have color,
   so drawing a shape means setting bits, and erasing means clearing bits.
Syntax of erase commands is identical to that of draw commands.
Erase commands are :
   erasepoint, eraselineseg, erasedisc, eraseline, erasecircle, erasearc.

In principle it is possible to undraw a shape from imobuf,
   provided that layer is not cleared after drawing,
   and that drawcolor has sufficient transparency (at least 25 %).
This could be usefull for filling areas delimited by temporary lines.
This is not implemented in this version of canvas.

Data transfer commands

flushlayer
Combines shape information in layer with value of 'drawcolor' variable
   and adds result to imobuf.
If opacity of drawcolor is 255, shape is painted over existing contents,
   otherwise shape is combined with existing contents as an overlay.
After this is done, content of layer is erased. (ie all bits are set to zero).

clearlayer
Erases content of layer.
This command is normally not used, as it is done automatically by flushlayer.

showimobuf
Reads contents of imobuf, converts it to format that X needs,
   stores it in an XImage, and tells X to display that XImage.
This is what causes content of imobuf to be visible on screen,
   and screen content does not change until this command is given.

showlayer $offx $offy
This is only usefull for debugging.
It reads layer, creates an XImage with same content, and makes X display it.
Result is magnified by factor 4, because there are 4x4 subpixels per pixel,
   and therefore only part of layer is shown,
   and which part is shown can be specified using arguments offx and offy
   which specify topleft pixel of result.

Operations on imobuf

setarea $x1 $y1 $x2 $y2
Sets all pixels to drawcolor
   in area of which (x1;y1) is topleft and (x2;y2) is bottomright.

copyarea $x1 $y1 $x2 $y2 $x3 $y3
Copies imobuf content from (x1;y1) to (x3;y3),
   and so forth for whole area of which (x1;y1) is topleft and (x2;y2) is bottomright.

Text

loadfont $fontfile
Loads font from given fontfile (if that exists)
   and unloads previously loaded font (if any).

drawtext $direction $x $y $text
Draws text string $text, using currently loaded font, and using drawcolor.
When drawing left to right, origin of first character is at position ($x;$y)
It draws directly to imobuf because glyphs of font have pixel granularity.
$direction can be r, l, u, or d (for right, left, up, or down).

Filling and counting

These commands are nearly not tested. I hope they work.
They operate on imobuf, not on on subpixel layer,
   so area to be filled can be bounded by shapes of different color.

fillarea $x $y
If pixel at (x;y) has a light color, it is filled with current drawcolor.
If pixel was filled and any left/right neighbour pixel has a light color,
   then these neighbours are also filled.
If any pixels on line were filled,
   and any of their up/down neighbours have a light color,
   then these neighbours are also filled.
As long as any pixels were filled,
   their neighbours are checked to see if they too can be filled.
Hopefully this always results in entire light-color area to be filled,
   but this routine can not be more precise than pixel granularity.
'Light color' is currently defined as :
   #define uchar unsigned char
   typedef struct { uchar o ; uchar r ; uchar g ; uchar b ; } orgb ;
   orgb* po ;
   if( (po->r) & (po->g) & (po->b) & 128 ){ *puc |= CANFILL ; }
This command replies with number of pixels that were filled appended to "+"
   if all went well.

countarea $x $y
Does same as fillarea, except that it does not fill any pixels.

Working with external applications that change imobuf

Canvas's routines will normally not try to update contents that has not changed.
If an external application changes content of imobuf via /tmp/canvasbuf.imo ,
   then it should let canvas know about this,
   otherwise a subsequent 'showimobuf' command would not actually do anything.

setaffected $x1 $y1 $x2 $y2
Tells canvas that contents of area (x1;y1)--(x2;y2) may have changed.

Loading and saving images

loadimofile $filename
Fills imobuf with content from file $filename,
   as far as it fits in canvas's window.
Filename must be fully qualified, else canvas looks for it in it's source directory.

saveas $filename
Saves entire content of canvas in file $filename .
Resulting file always has .imo fileformat.
If filename is not fully qualified, it is considered to be relative to /tmp .

savearea $x1 $y1 $x2 $y2 $filename
Saves content of specified area into file $filename.
Area's topleft is (x1;y1) and it's bottomleft is (x2;y2) ,
   apart from that, this command is identical to 'saveas' command.

Speedup buffers

storeximage $buffernumber
Stores content of main XImage into ximage buffer number $buffernumber.
If there is not yet a buffer with that number, it is created.
It is user's responsibility to free memory of such buffers when no longer needed
   for which they can use command 'freeximagebuf'.

loadximage $buffernumber
Copies content of ximage buffer $buffernumber to main XImage, and makes X render it.

freeximagebuf $buffernumber
Frees storage allocated for ximage buffer $buffernumber.

Canvas currently does not provide extra imo buffers.

Events

recordclickdrag
Makes canvas start recording click, doubleclick, and drag events.

norecordclickdrag
Makes canvas stop recording events.

reportclickdrag
Makes canvas reply with all events recorded so far
   (and clears list of recorded events).

Terminating canvas

quit
Terminates canvas, removing socket file and mmap of imobuf.

File formats

File format of .imo files is described here.
File format of .gly files is described here.

Development

First need i had for a program like this occurred about 2 years ago ;
   I wanted to draw a picture of a parabolic mirror,
   but none of programs i found was capable of that.
Next need was a desire to add pictures to my tutorials, mainly block schematics.
I fruitlessly tinkered around with various "solutions",
   but in the end, i wanted something that could draw anything that i can compute,
   and it is impossible to write a drawing program that
   is equally good at computing as a programmer.
So when i made canvas, i was very happy to have found the right APPROACH :
   a program that draws as commanded, with a simple interface.
Whether that approach is implemented as a library or as socket communication
   doesn't matter much ;
   Socket way is a bit slower (no problem since i wanted to draw static pictures)
   but has advantage that it is already supported by many programming languages
   (such as Perl).

When i started writing canvas, i had a lot of unfinished projects that
   depended on better understanding of and basic code for X.
So i had made an X tutorial project to be center of development,
   and it had progressed to where i could draw in colour
   (in all videomodes that are still in use, including 8 bit per pixel).
Next topics for that project to handle are :
   drawing in gryscale (because i believe X's gamma correction is wrong),
   scrolling (for which i did few experiments to find best way to do it),
   fonts (because i want access to glyphs, amongst others),
   and events (which i already use a lot,
   but i want better handling of keyboard events).
Canvas was therefore developed on top of an incomplete infrastructure,
   and it shows.
I already used imo file format in some picture-modification programs
   (and the 'offset' fields in imoheader are because i couldn't get gimp to
   put a part of image that i had modified back exactly in location it came from).
   This is fairly mature code.
I already used client-side fonts, but this is much less mature.
Scrolling is not implemented in canvas yet,
   partly because it could cause very large buffersizes,
   so it requires experiments to determine optimum tilesize to store in filesystem.
   Perhaps implementing all buffers as mmapped files would be a solution.
Another thing that is not present is ability to resize window ;
   this is relatively simple to add after scrolling is implemented.

While creating canvas, i found some usefull additions that were easy to implement.
Mmapped imobuf is one of them ; it came nearly automatically,
   all i had to do was give it a name in filesystem.
Ability to add a stack of XImages is other one ;
   it came about because i had socket problems, so added timing measurement,
   and thus got interested in how well canvas could be used for animations.
Not implemented is ability to have a stack of imobufs,
   which would be usefull for off-image storage, and for reusing intermediate results.
I also added event-reporting (which is actually very easy to add)
   and with that it can be used as frontend to interactive applications,
   presenting a very simple interface to programmer
   that takes no additional time to learn.

Canvas is a very usefull program,
   and that is why it should be in Debian (imnsho),
   but it is not a very mature program.
With this i don't mean that it has a lot of bugs (which seems likely),
   but that it's design is immature, especially it's draw mode.
Currently, when a shape is transferred from subpixel-layer to imobuf,
   it is combined with contents of imobuf as an overlay :



As you see, combining colors is not additive.
Also, it is not really like a transparent overlay,
   because for that it would need to have both transparency and opacity
   and it would need them for each r,g,b component.
Another effect is that it is not possible to make part of image transparent ;
   drawing with transparent drawcolor merely adds a transparent layer on top.
This also causes inability to erase things ;
   commands like 'eraseline' erase line's shape from subpixel-layer, not from imobuf,
   and this layer can only be combined with imobuf as an overlay,
   it can not be used to erase shapes in imobuf
   (although it can paint with background color, it can not make transparent).
   Exceptions are setarea and copyarea, but these can only handle rectangular areas.
A next version may change this, by adding new specifiable draw modes,
   and may also change color model from 'drawing on white background'
   to 'drawing on transparent and showing result against white background'
   (which would cause imobuf to no longer contain background color).
Support for gradient-fills is also absent.

I expect that next things that will be added are
   simple programs that use canvas as graphics frontend
   such as a free-hand drawing program (for making a quick sketch),
   and a ruler-assisted drawing program (for neat sketches).

Canvas still lacks some desirable features,
   but what it does provide is very usefull :
   drawing and filling in solid color,
   using transparent coloured overlays of any shape,
   adding simple texts,
   being interactive, and
   being able to draw anything that you can compute.

   TRY IT !
   It is easy !

Copyrights

All code was written by me, Siward de Groot, between 2000 and 2010.
It would not have been possible without code and docs i got from Debian, X and GNU.
I hereby copyright this software under terms of GNU General Public License version 3,
   which means that
* You can copy, modify, and share it,
   but if you do that, then you must
     * make source-code of those things available to all users of those things,
     * and provide those things under same copyright conditions as original
* There is no warranty of any kind.
There is however one additional copyright condition :
* You are not allowed to pretend anyone else than me wrote this
   (ofcourse you can take credit for any improvements you made yourself).

If you do make modifications, i would like to hear about it,
   and same applies to any constructive criticism you might have.
I can be reached at siward@@ziggo..nl (with single @ and . ofcourse).