At one point in time, I had to find a way to digitize 100+ shapes into text formatted packages to be used as input for a computational fluid code. Necessity being the mother of invention, I came up with a poor man's digitizer using a free program called ImageJ. (Located at: http://rsbweb.nih.gov/ij/)

The nice thing about ImageJ is that it has a built-in C-style macro language, which is what I used to write the digitizer. For the impatient, the program listing for the macros are at the bottom of this page. Obviously, they are going to have to be modified to suit your purposes.

As a side note, there exist two decent 2D digitizers that I am aware of. Neither are for digitizing generic shapes but both are for generating value pairs from 2D graphs. They are Engauge and Plot Digitizer, and both take a slightly different approach to digitizing data.

The Steps:

Drawing a line in ImageJ.
ImageJ will remember the length
of this line.

To begin digitizing images, first load the image that you're going to be digitizing from. If you want to set the scale of a loaded image, use the line tool to draw a line along the length of a known distance in the image.

ImageJ Set Scale window.
'Distance in Pixels' will already be filled
in with the length of the last line you
drew. 'Unit of Length' isn't too important
here.

Then go to to 'Analyze' pull-down menu and select 'Set Scale.' Change the 'Known Distance' to the real world representation for the line you just drew and, if you want, the 'Unit of Length.' Note that when drawing the line for setting the scale, it is best to pick the largest knowable distance to increase accuracy. Also, be aware of perspective. If the object you're scaling the image to is in a different plane than the object(s) you're digitizing, the accuracy of the digitized packages is going to suffer. It doesn't take much to throw off your measurements.

Path to macro installer in ImageJ
You'll only need to install the macro once.

Next, load either the macro's that are displayed in the listing at the bottom of this post, or your own custom version of the listing below. Go to the 'Plugins' menu, then the 'Macro' sub-menu and select 'Install.' An open file dialog box will open up and allow for you to navigate to the file with the macro's in it. Any time you re-open ImageJ or make a change to the macro, you're going to have to re-install it. Remember that if you change a macro on the fly you're going to have to re-install it from this menu. The newly available macro functions will appear by name below the line break in the 'Macro' sub-menu under 'Plugins.'

Setting the origin.
The first two steps in setting the origin.

With the image and the macro's loaded, the origin will need to be set before anything else will work. This is going to be the origin for a typical Cartesian grid. Use the 'Point selection' tool on the ImageJ toolbar to put a point on the image where you want the origin to be. Go to 'Plugins->Macro->setOrigin', or just hit 's' on your keyboard, and the origin will now be set where the point is. If you'll notice, there is an 's' between square brackets next to the words 'setOrigin.' That tells you that you can just hit that letter as a keyboard shortcut to run the macro. The origin needs to be set only once at the beginning of the session.

Two digitized packages and their results.
A pair of packages and their output in the Log window

Finally, its time to generate your packages. Use either the square or polygon selection tools to draw a shape. Then go to 'Plugins->Macro->getData', or hit the 'g' shortcut on your keyboard. The 'getData' macro will output the information out to a log window and fill in the shape that was drawn so you know what packages have been generated.

When you're done, save the contents of the log file ('file->save'.) You'll have to do any post processing by hand unless you can embellish the code enough for your purposes.

The code listing below can be copied and pasted into a text file. If you've read this far, you probably already know this, but don't forget to use a text editor such as Notepad, vi, etc.. The lines that have '{change}' in the comments are the output of the digitizer itself. For every package, I have a header, the body and a footer for each run.

digitizer.txt

// Setting up global variables
//  ox, and oy are the origin x and y positions.
//  isOriginSet is used to determine if the origin is set or not
var ox,oy,isOriginSet=false;

// This macro sets the global origin values based off of
// where the point selection is on the screen.  If multiple
// points have been selected, then it uses the first point.
macro "setOrigin [s]" {
  requires("1.30k");
  if(selectionType==10){
    isOriginSet=true;
    getSelectionCoordinates(xar,yar);
    ox=xar[0];
    oy=yar[0];   
  }
}

// If the origin has been set using the 'setOrigin' macro, this
// macro will grab the points from either the polygon or square
// selection tools, do a little math to translate the points
// to real world coordinates, and prints the output of interest.
macro "getData [g]" {
  requires("1.30k");
  if(isOriginSet){
    // 'selectionType==0' is for the square selection
    // 'selectionType==2' is for the polygon.
    // 'selectionType==3' will do Freehand selections if
    // you want that many datapoints.
    if(selectionType==0 || selectionType==2){
      getPixelSize(unit,pixelWidth,pixelHeight);
      getSelectionCoordinates(xar,yar);

      print("");                     // blank line
      print("#some comments");       // comments, etc..      {change}
      print("package header");       // package header info  {change}
      print("numpts: "+xar.length);  // output number of pts {change}

      for(i=0; i<xar.length; i++){
        // X is the screen's X minus the origin's X
        // Y is the origin's Y minus the screen's Y (because Y is increasing as
        // you go down in the image.)
        x=xar[i]-ox;       y=oy-yar[i];
        px=x*pixelWidth;   py=y*pixelHeight;

        print(px+","+py);          // output datapoint {change}

      }
      getSelectionBounds(xx,yy,ww,hh);
      ulx=(xx-ox)*pixelWidth;     uly=(oy-yy)*pixelHeight;
      llx=(xx+ww-ox)*pixelWidth;  lly=(oy-yy-hh)*pixelHeight;

      print("UL-Bounds: "+ulx+","+uly);
      print("LL-Bounds: "+llx+","+lly);

      fill();  // This fills in our selection so we know what we've
               // already digitized.
    }
  }
}

Some tips, tricks and additional information: