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:
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:
Please consider supporting my iToolBox development efforts with a small PayPal donation. |