Operators Dialog / Operator Details / Notations

§ 12.11.63.5 - Gallery Example for Developers

This page provides a demonstration of how one might use the JSON output of the Notations operator. It presents sample Javascript code that reads an image and the JSON generated by iToolBox to annotate the image. You are certainly free to copy and modify this code; it's here for exactly that purpose.

For the demo, there are two files. These are in the gallery folder:

  • ben_airport.jpg — the image. The name, without the extension, is used to find the JSON, next:
  • ben_airport.json — result of Write JSON in the Notations operator.
Note: In this example, I have eliminated the database integration specifically to keep the demo both easier to grasp and the code free of database dependencies. Basically, the gallery folder will contain image files, likely either .png and/or .jpg/.jpeg, because those are the image formats the browsers understand, and JSON files with matching filenames generated by iToolBox. You'd modify the getData() function in the Javascript code and that's probably all.
Tip: This HTML and Javascript code depends on the HTML 5 canvas element. Modern browsers should have no trouble. Also, the HTML 4 validation at the bottom of the page will fail because this element is included in the demo. smile

Mouse over the image to see the notes (or, in a touchscreen environment, touch the button underneath the image):




Here's the HTML code in the body of the page. You'll note there's nothing specific in it as to what image is to be displayed; that's taken care of in the Javascript. You just drop it on the page and that's the end of that. For use in an actual image gallery, you'd provide some way to feed the viewer's current location in the gallery to the database, and it would simply return the name of the image. From there, the rest of the process picks up the image and the notations and you're good to go. Here it is:

<div id="picdiv" style="text-align: center;">
<img id="mypic">
<canvas id="picCanvas" width="0" height="0" onmouseenter="mousingover()" onmouseout="mousingaway()"></canvas>
<p style="margin-left: 0;"><span id="mypictext"></span><br>
<button id="forTouchDevices" onclick="doManual()">Touchscreen? Touch here for notes.</button></p>
</div>

Here's the Javascript that implements the demo gallery. It is placed inside the <HEAD> and </HEAD> tags:

<script type="text/javascript">
var xdim;
var dismode;
var working;
var mousehover;

    xdim = -1;
    dismode = 0;
    working = 0;
    mousehover = 0;
    tcounter = -1;

// getData() is the function to replace with your database-fetching code. Here, I've
// simply hard-coded in the data and returned the array. You could just hard-subsitute
// the data here using CGI to generate the page, and then every gallery page would do
// the right thing:
function getData()
{
var ray = ["ben_airport.jpg",
"This is me at the Matamoras, PA airport<br>probably around 1975 or so",
"270",
"648",
"Ben at airport"];
    return ray;
}

// doManual() -- this function responds to clicks or touches on
// the button below the image. The button serves to provide a
// "display notes" command in lieu of mouse input in a touch
// environment:
function doManual()
{
    if (dismode == 1)
    {
        show_image('mypic'); // flip to no notes
    }
    else
    {
        show_image_notes('mypic'); // flip to notes
    }
}

// mousingover() is called when the mouse enters the area over the image and its
// drawing context; it fires off a drawing with notes:
function mousingover()
{
    if (dismode == 1) return;
    show_image_notes('mypic');
}

// mousingaway() is called when the mouse leaves the area over the image and its
// drawing context; it fires off a drawing without notes:
function mousingaway()
{
    if (dismode == 0) return;
    show_image('mypic');
}

// ticker() is a timer routine that is constantly running.
// When the display is rescaled, the width of this div changes.
// This is used to re-fire the calculation of where the notes go:
function ticker()
{
    var myAnchor = document.getElementById('picdiv');
    var xd = myAnchor.offsetWidth;
    if (working == 0 && xd != xdim)
    {
        working = 1;
        if (dismode == 1) show_image_notes('mypic');
        else              show_image('mypic');
        xdim = xd;
        working = 0;
    }
}

// syncCallAjax() provides a means to read files from the host without
// being bothered by callbacks and having to deal with the inevitable
// timing issues they bring. So this is synchronous - result returns only
// when called script provides it:
// ------------------------------------------------------------------------
function syncCallAjax(filename)
{
var remote = '__Unset__';
var request = new XMLHttpRequest();
var remote_url;
 
   request.open('GET', filename, false);  // false makes the request synchronous
   request.send(null);
   if (request.status === 200)
   {
       remote = request.responseText;
   }
   return(remote);
} // end syncCallAjax() function

// atloadtime() is called when the page loads. It sets things up and
// does the initial image display for us:
function atloadtime()
{
    xdim = -1;
    dismode = 0;
    working = 0;
    mousehover = 0;
    show_image('mypic');
    setInterval(function(){ ticker(); }, 500);
}

// And here's what kicks everything off:
window.onload = function() { atloadtime(); }


// draw_text() displays the actual notes. It is called after the
// area selections are drawn, so that the text always overlays
// them. It is entirely possible to create notes that overlay one
// another, so this ordering at least prevents the areas themselves
// from interfering with reading the notes. This function provides
// the text background at the desired opacity, and arranges the
// text by line if required:
function draw_text(ax,ay,ex,ey,pic,mode,txt,context,fh,opa)
{
var yrad = ey - ay;
var ph = pic.height;
var ceny = ph / 2;
var pw = pic.width;
var xpos,ypos,cy;
var tlines = txt.split('\n')
var len = tlines.length;
var i;

    // determine max length by checking every line
    var maxlen = -1;
    var tlen;
    for (i=0; i<len; i++)
    {
        txt = tlines[i];
        tlen = context.measureText(txt).width;
        if (tlen > maxlen)
        {
             maxlen = tlen;
        }
    }
    var bu = fh; // this is even so it will pad both sides identically
    var hbu = bu / 2;
    var yfat = (len * fh)+bu; // font point size
    maxlen += bu; // the width of the box

    if (mode == 'Ellipse')
    {
        xpos = ax;
        if (ay > ceny) // if center on bottom half of image
        {
            ypos = ay - (yfat + yrad); // text at top of figure
        }
        else // center on top half of image
        {
            ypos = ay + yrad + fh; // text at bottom of figure
        }
    } // end IF ellipse
    else if (mode == 'Polygon' || mode == 'Rectangle' || mode == 'Freehand')
    {
        xpos = ax + ((ex - ax) / 2);
        cy = ay + ((ey - ay) / 2);
        if (cy > ceny) // if center on bottom half of image
        {
            ypos = ay - yfat; // text on top of figure
        }
        else // center on top half of image
        {
            ypos = ey + fh; // text on bottom of figure
        }
    } // end ELSE IF polygon / rectangle / freehand

    xpos -= (maxlen / 2);
    context.fillStyle="#FFffFF";
    context.globalAlpha = opa;
    context.fillRect(xpos,ypos-hbu,maxlen,yfat);
    xpos += hbu; // the text indent
    ypos += 10; // offset from top of box
    context.fillStyle="#000000";
    context.globalAlpha = 1.0;

    for (i=0; i<len; i++) // display the text line by line
    {
        txt = tlines[i];
        context.fillText(txt,Math.round(xpos),Math.round(ypos));
        ypos += fh;
    } // end FOR each line
} // end draw_text() function

// draw_rectangle() - notation area drawing
function draw_rectangle(ax,ay,ex,ey,context)
{
    context.beginPath();
    context.rect(ax,ay,ex-ax,ey-ay);
    context.stroke();
    context.closePath();
} // end draw_rectangle() function

// draw_polygon() - notation area drawing
function draw_polygon(pointlist,context)
{
var prevx = pointlist[0].x;
var prevy = pointlist[0].y;
var len = pointlist.length;

    context.beginPath();
    for (i=0; i<len; i+=1)
    {
        x = pointlist[i].x;
        y = pointlist[i].y;
        context.moveTo(prevx,prevy);
        context.lineTo(x,y);
        prevx = x;
        prevy = y;
    } // end FOR each XY point
    context.stroke();
    context.closePath();
} // end draw_polygon() function

// draw_ellipse() - notation area drawing
function draw_ellipse(centerX,centerY,ex,ey,pic,context)
{
var xrad = ex - centerX;
var yrad = ey - centerY;
var pw = pic.width;
var ph = pic.height;
var larger = pw;
var pi = Math.PI;
var x,y,i,incr,xprev,yprev;
var twopi = pi *2;

    if (ph > pw) { larger = ph; }
    incr = pi / (3 * larger); // we need to keep the steps between lines relative to image dimensions
    xprev = centerX + (xrad * Math.cos(0)) + 0.5;
    yprev = centerY + (yrad * Math.sin(0)) + 0.5;
    context.beginPath();
    for (i=0; i<twopi; i+=incr) // step around the ellipse
    {
        x = centerX + (xrad * Math.cos(i)) + 0.5;
        y = centerY + (yrad * Math.sin(i)) + 0.5;
        context.moveTo(xprev,yprev);
        context.lineTo(x,y);
        xprev = x;
        yprev = y;
    } // end FOR around ellipse path
    context.stroke();
    context.closePath();
} // end draw_ellipse() function

// show_image() displays the image without any notes:
function show_image(ele)
{
// First, we fetch and parse the data from the data file. You'd
// want to get this from a database:
// ------------------------------------------------------------
dismode = 0;
var ray = getData();
var src = ray[0];
var desc = ray[1];
var width = ray[2];
var height = ray[3];
var alt = ray[4];
var path = 'gallery/';

    // Here, we use the fetched data to set up the gallery page:
    // ---------------------------------------------------------
    var imgName = path+src;
    src = src.replace(/\.[^/.]+$/, ""); // strip the extension off the image name
    var myAnchor = document.getElementById(ele);
    var theImage = ele+'text';
    var img = document.createElement("img");

    img.src = imgName;
    img.width = width;
    img.height = height;
    img.alt = alt;
    img.id = 'mypic';
    myAnchor.parentNode.replaceChild(img, myAnchor);
    var pic = document.getElementById(ele); // get ready element
    document.getElementById(theImage).innerHTML = desc;

    // The following is all prep to clear the canvas:
    var cnvs = document.getElementById("picCanvas");
    cnvs.width = pic.width;
    cnvs.height = pic.height;
    cnvs.style.position = "absolute";
    cnvs.style.left = pic.offsetLeft + "px";
    cnvs.style.top = pic.offsetTop + "px";
    var context = cnvs.getContext("2d");
    context.clearRect(0, 0, cnvs.width, cnvs.height); // erase the notes, if displayed
} // end show_image() function

// show_image_notes() displays the image with notes.
// This is the core of the gallery code. First it gets the
// image; then it sets up a drawing context; then, using the
// JSON data, it steps through the area definitions and calls
// the appropriate drawing routine, then finally it steps
// through and draws the actual notes that go with the areas:
function show_image_notes(ele)
{
// First, we fetch and parse the data from the data file. You'd
// want to get this from a database:
// ------------------------------------------------------------
dismode = 1;
var ray = getData();
var src = ray[0];
var desc = ray[1];
var width = ray[2];
var height = ray[3];
var alt = ray[4];
var path = 'gallery/';

// Here, we use the fetched data to set up the gallery page:
// ---------------------------------------------------------
    var imgName = path+src;
    src = src.replace(/\.[^/.]+$/, ""); // strip the extension off the image name
    var myAnchor = document.getElementById(ele);
    var theImage = ele+'text';
    var img = document.createElement("img");
    img.src = imgName;
    img.width = width;
    img.height = height;
    img.alt = alt;
    img.id = 'mypic';
    myAnchor.parentNode.replaceChild(img, myAnchor);
    var pic = document.getElementById(ele); // get ready element
    document.getElementById(theImage).innerHTML = desc;

    // now we go get the JSON notation data:
    var theJson = syncCallAjax(path+'ben_airport.json');
    theObjects = JSON.parse(theJson);

    // The following is all prep for drawing:
    var cnvs = document.getElementById("picCanvas");
    cnvs.width = pic.width;
    cnvs.height = pic.height;
    cnvs.style.position = "absolute";
    cnvs.style.left = pic.offsetLeft + "px";
    cnvs.style.top = pic.offsetTop + "px";
    var context = cnvs.getContext("2d");
    context.clearRect(0, 0, cnvs.width, cnvs.height);
    var fh = 12;
    context.font = fh.toString() + "px Arial";
    context.strokeStyle="#FF0000";
    context.fillStyle = "#FFffFF";
    context.textAlign = "left";

    // and here we work through the areas of the notations:
    var arrayLength = theObjects.Notations.length; // this is how many notations there are
    for (var i=0; i<arrayLength; i++) // draw all the areas
    {
        var am = theObjects.Notations[i].areaMode;
        var ax = theObjects.Notations[i].anchorX;
        var ay = theObjects.Notations[i].anchorY;
        var ex = theObjects.Notations[i].extentX;
        var ey = theObjects.Notations[i].extentY;
        var txt = theObjects.Notations[i].text; 

        if (am == 'Ellipse')
        {
            draw_ellipse(ax,ay,ex,ey,pic,context);
        } // end IF ellipse
        else if (am == 'Polygon' || am == 'Freehand')
        {
            draw_polygon(theObjects.Notations[i].pointList,context);
        } // end IF polygon or freehand
        else if (am == 'Rectangle')
        {
            draw_rectangle(ax,ay,ex,ey,context)
        } // end IF rectangle
    } // end FOR area displays

    // now we do the text display on top of the area indications:
    for (var i=0; i<arrayLength; i++)
    {
        var am = theObjects.Notations[i].areaMode;
        var ax = theObjects.Notations[i].anchorX;
        var ay = theObjects.Notations[i].anchorY;
        var ex = theObjects.Notations[i].extentX;
        var ey = theObjects.Notations[i].extentY;
        var txt = theObjects.Notations[i].text; 
        var opa= theObjects.Notations[i].opacity;
        draw_text(ax,ay,ex,ey,pic,am,txt,context,fh,opa);
      } // end FOR text displays

} // end show_image_notes() function
</script>

Lastly, (and just for reference, iToolBox builds this information, so you need not worry about it) here's the content of the JSON file, ben_airport.json — note that the filename matches the image name. The image name is stripped of its extension, then .json is added, and that's how the gallery locates the JSON data you see here:

Document Keyboard Navigation
, Previous Page . Next Page
t TOC i Index k Keyboard o Operators g Glossary c Changes
This manual was generated with wtfm
wtfm uses aa_macro and SqLite
wtfm and aa_macro are coded in python 2.7
iToolBox 3.12
This Documentation and Associated Application Executables are Public Domain
Please consider supporting my iToolBox development efforts with a small PayPal donation.