Summary: imagemap.js is a ‘reactive’ javascript alternative to HTML image maps (i.e. the user is shown what he or she is getting).
This page documents imagemap by showing how to produce the
image using it. The main account applies to images in HTML
pages, requiring the addition of a little javascript. A further paragraph
explains how to invoke imagemap from pure javascript pages. The source
code for this page itself is an example of using imagemap in HTML;
our Cordillera Blanca intro is
slightly simpler in not having a title and in the image not being a link.
Our home page is a fairly elaborate piece of javascript using imagemap.
There are probably hundreds of similar modules all round
the web. Google doesn’t find them for me. The only one I know of is the
jQuery-based
imagemapster,
whose aims are rather different.
imagemap doesn’t require a state-of-the-art browser or any
external libraries. It works correctly for me on a Mac which claims to have been
built in ‘Late 2006’.
A couple of lines in the HTML header provide relevant definitions. Firstly we
load the script by
You can do exactly this, but it is better to make a local copy of
imagemap to avoid an unnecessary external dependence.
And secondly, imagemap images are often links, but can only
be seen as links if they have borders, which need to be requested in some way.
On this page I define a ‘bordered’ class in the initial style definition:
An earlier version of imagemap required the image to be a link.
This is no longer necessary. It works almost perfectly for images which are not
links (treating them as empty links).
At the point where the image is needed, you do not supply the image itself
but rather a ‘div’ which will be overwritten by it.
This is standard HTML. Notice that we supply an ‘id’ for both the div and the
element containing the header text. These will be used to tell the javascript which
elements to operate on. We also supply a size and background colour for the div. These
aren’t essential. The size saves the browser from having to adjust the layout when the
div is overwritten. The grey background avoids leaving a suspicious blank until the
image has been loaded.
The size is not the size of the image, but the size of a
minimal container capable of holding it, making allowance for the border,
padding and margin. In this case the border is 1 pixel and the padding 2, so the
div is 6 pixels wider and higher than the image itself.
Finally, at the end of the page after the ‘</body>’ and
before the ‘</html>’, you provide the javascript. Not much is
needed. The whole of it, first data then code, is as follows:
It is the data component which most needs explanation. Ignore the variable
‘stem’, which is simply used to abbreviate my URLs, which are longer than most
people ever need worry about.
The main piece of data is the object – assigned here to the variable
gpsim – which defines the image containing mapped regions. Its obligatory fields are:
If there are no additional fields in the map object, then there will be no
mapped regions inside the image.
The optional fields of a map object are:
Some further fields will be added by
imagemap during its processing.
The region fields are:
The coordinates are an array of 3, 4, 5 or 2n numbers with
n≥3.
The map contains examples of all 4 shapes. The polygon (Alta Rezia) is
exaggeratedly ugly to illustrate the fact that regions need not be convex.
Two structures are used to pass style information.
The image style contains style information about the image. Its
fields are:
touchBorderOpacity is provided to make map regions visible on
touch-screen devices where hovering cannot be detected, and where the normal
borders and outfill therefore cannot be drawn reactively. On such devices it is
a good idea to draw an unobtrusive permanent border round all the regions.
This can be requested by supplying a non-zero touchBorderOpacity, which
will be applied in conjunction with the standard borderColor, but
only on touch-screen devices. The effect is as shown on the right.
Setting the the map object field debug to ‘touch’ tells
imagemap to emulate touch-screen processing for the map in question; this
is how the diagram on the right is obtained. You will find that clicking in the
different regions acts as for an HTML image map, even though there’s no
reactive response.
The image is generated in 3 steps. The first is written as
which returns a div containing a mapped image according to the supplied
parameters.
The second step is to put the div into the document in place of its
placeholder. The placeholder is identified by its ‘id’ – in this case ‘imgdiv’ –
so we find it by a call to getElementById(), and then replace the
placeholder as a child of its parent by the newly created div.
It would be slicker to do this in a single line:
but the replaceWith() function was not added to browsers until 2016, so you
will be cutting off users with very old computers.
The final step is optional, and is needed only if you want the image title to
be changed to reflect the region hovered over. You call
The first argument is the map object holding information relative to the image, and
the second is the element whose inner HTML needs to be changed, found from its ‘id’.
If you do not supply a title to update, the titles of the mapped regions will be
displayed as tooltips.
imagemap was written for our home page, which
nowadays is pure javascript. Take a look at the source code to see the full process. The
component of interest is the function addgps whose current source code is this:
div is a div supplied to addgps to which the new image will be added.
i is the name of the image, which is converted to an index in an array by the
call to gpsind. There follows some conditional code: addgps may be called
repeatedly as the page is reconstructed following resizing, but we don’t want to repeat
work unnecessarily, especially if it may give rise to HTTP requests, so the mapped images
are stored in a static array once created. marg is a variable passed to
parametrise the top margin.
To add an image to the div, we start by creating an element containing the
title. We use the ‘title’ field of the map object to store the title we will use, knowing
that it will be subsequently overwritten by the title as actually stored in the document
(which will be equivalent as HTML, though not necessarily as a character string). We
place this element in the
map object by calling addtitletomappedimage, which puts it in the
titlediv field. Then we create the mapped image
itself by calling genmappedimage. Notice that the image inherits its style from a
global css declaration, so the only map style fields we need to supply are x and
y.
To add the title and image to the div, we then successively append the
corresponding elements, the first of which has been stored in the titlediv field
of the map object, and the second of which has been kept in a static array.
As mentioned earlier, imagemap stores values in the map object passed to it as
well as reading values from it. It overwrites the following fields:
Email me at colin·champion&routemaster·app,
substituting full stops for the dots and an ampersat for the ampersand.
imagemap is provided under an MIT licence permitting free use and
modification. It does the things I wanted it to do for our home page; other
features might be both useful and easy to add. Feel free. Let me know how you get on.
• genmappedimage
• mousefactory
• function
• leavefactory
• clickfactory
• addtitletomappedimage
• outpoint
• findregion
• pointinpoly
• isleft
• getbordercoords
• getp
• drawshape
• getpt
• imagetwitch
• setlink
The image on the right illustrates the use of imagemap: hover over the
coloured routes to see how it responds. Clicking in one of these areas takes
you to a region-specific link, whereas clicking elsewhere takes you to the
main image link. The title tells you where you are going. (The URL line on the
bottom-left of your browser window may give you the full location. In Chrome it
does so; Firefox has a minor bug in that it doesn’t update the display when the
URL is changed by the script.)
North Italy
<script src="https://www.masterlyinactivity.com/imagemap.js"></script>
img.bordered{border:1px solid;padding:2px}
<p><table cellpadding=0 cellspacing=0 style="float:right;margin:6px 0 6px 6px">
<tr><td align=left><b id=imghead>North Italy</b>
<tr><td><div id=imgdiv style="width:180px;height:106px;background-color:grey"></div></table>
<script>
// data
var stem = 'https://www.routemaster.app/?track=' +
'https://www.masterlyinactivity.com/routemaster/routes/' ;
var gpsim = { imgurl:'https://www.masterlyinactivity.com/includes/NorthItaly.gif' ,
srcset:'https://www.masterlyinactivity.com/includes/NorthItaly@h.gif 2x' ,
w:174 , h:100 ,
linkurl:stem+'NorthItaly.rte' ,
map: [ { title:'Lake Como' ,
linkurl:stem+'altarezia16/larioindex.rte' ,
coords:[45,60,55,40,25] } ,
{ title:'L. Garda (north)' ,
linkurl:stem+'garda/NGarda.rte' ,
coords:[145,65,12] } ,
{ title:'L. Garda (east)' ,
linkurl:stem+'garda/EGarda.rte' ,
coords:[135,85,12] } ,
{ title:'Alta Rezia' ,
linkurl:stem+'altarezia16/index.rte' ,
coords:[80,30 , 120,40 , 110,20 , 140,35 , 120,0 , 85,5 ] } ,
{ title:'L. Maggiore' ,
linkurl:stem+'maggiore/LagoMaggiore.rte' ,
coords:[15,55,20] } ,
{ title:'Lake Iseo' ,
linkurl:stem+'iseo/LagodIseo.rte' ,
coords:[80,50,110,90] } ] } ;
var regionstyle = { borderColor:"#aa0000" , borderWidth:1 ,
outfillColor:"grey" , outfillOpacity:0.4 ,
touchBorderOpacity:0.4 } ;
var mapstyle = { class:'bordered' , x:3 , y:3 } ;
// code
var img = genmappedimage(gpsim,mapstyle,regionstyle) ;
var imgel = document.getElementById('imgdiv') ;
imgel.parentNode.replaceChild(img,imgel) ;
addtitletomappedimage(gpsim,document.getElementById('imghead')) ;
</script>
The region style determines the appearance of a region
hovered over. Its fields are: borderColor,
borderWidth, outfillColor,
outfillOpacity and touchBorderOpacity. These specify
the width and colour of the border and the colour and opacity of the outfill,
i.e. of the fill applied to the image outside the region hovered over. Setting
the width/opacity to 0 disables the border/outfill. (Don’t attempt east Atlantic
spelling.)
North Italy
img = genmappedimage(gpsim,mapstyle,regionstyle) ;
var imgel = document.getElementById('imgdiv') ;
imgel.parentNode.replaceChild(img,imgel) ;
document.getElementById('imgdiv').replaceWith(img) ;
addtitletomappedimage(gpsim,document.getElementById('imghead')) ;
function addgps(div,i,marg)
{ var i , map , p ;
i = gpsind(i) ;
if(!addgps.ims) addgps.ims = new Array(gpsims.length) ;
if(!addgps.ims[i])
{ p = document.createElement('p') ;
p.innerHTML = gpsims[i].title ;
addtitletomappedimage(gpsims[i],p)
p.setAttribute('style','margin-top:'+marg+'px') ;
addgps.ims[i] = genmappedimage(gpsims[i],{x:3,y:3},regionstyle) ;
}
div.appendChild(gpsims[i].titlediv) ;
div.appendChild(addgps.ims[i]) ;
}
/* ------- https://www.masterlyinactivity.com/software/imagemap.html ------- */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function genmappedimage(image,imagestyle,regionstyle)
{ function mousefactory(img)
{ return function(e)
{ imagetwitch(e,img,regionstyle.borderColor,regionstyle.borderWidth,
regionstyle.outfillColor,regionstyle.outfillOpacity) ;
} ;
}
function leavefactory(img)
{ return function(e)
{ imagetwitch(null,img,regionstyle.borderColor,regionstyle.borderWidth,
regionstyle.outfillColor,regionstyle.outfillOpacity) ;
} ;
}
function clickfactory(img)
{ return function(e)
{ var i = findregion(e,image) ;
if(i!=null) image.canlink.setAttribute('href',image.map[i].linkurl) ;
} ;
}
var c,dpr,i,k,p,pdash,q,n,m=image.map,mini,minx,mi,wise,co ;
// create the image
image.div = document.createElement('div') ;
image.div.style.position = 'relative' ;
image.img = document.createElement('img') ;
if(imagestyle&&imagestyle.class)
image.img.setAttribute('class',imagestyle.class) ;
if(imagestyle&&imagestyle.style)
image.img.setAttribute('style',imagestyle.style) ;
image.img.setAttribute('width',image.w) ;
image.img.setAttribute('height',image.h) ;
if(image.srcset) image.img.setAttribute('srcset',image.srcset) ;
image.img.setAttribute('src',image.imgurl) ;
image.link = document.createElement('a') ;
image.link.setAttribute('href',image.linkurl?image.linkurl:'#') ;
if(!image.linkurl) image.link.style.cursor = 'default' ;
image.link.appendChild(image.img) ;
image.div.appendChild(image.link) ;
if(!image.map) return image.div ;
// remaining code is for handling image map if provided
// put the coords into canonical form
for(mi=0;mi<m.length;mi++)
{ p = m[mi].coords ;
if(p.length&1)
{ if(p.length==5&&p[0]>p[2])
m[mi].coords = [ p[2],p[3] , p[0],p[1] , p[4] ] ;
continue ;
}
n = p.length / 2 ;
if(n==2)
{ m[mi].coords = [ Math.min(p[0],p[2]) , Math.min(p[1],p[3]) ,
Math.max(p[0],p[2]) , Math.max(p[1],p[3]) ] ;
continue ;
}
// remove end pt if a dupe of start
while(n>=2&&p[2*(n-2)]==p[2*(n-1)]&&p[2*(n-2)+1]==p[2*(n-1)+1]) n -= 1 ;
// reorder so that leftmost point comes first
for(i=0;i<n;i++) if(i==0||p[2*i]<minx) { mini = i ; minx = p[2*i] ; }
pdash = new Array(2*n) ;
for(k=0,i=mini;k<n;i++,k++)
{ if(i==n) i = 0 ; pdash[2*k] = p[2*i] ; pdash[2*k+1] = p[2*i+1] ; }
p = pdash ;
// reorder the points anticlockwise
for(q=(p[0]-p[2*(n-1)])*(p[1]+p[2*(n-1)+1]),i=0;i<n-1;i++)
q += (p[2*(i+1)]-p[2*i]) * (p[2*(i+1)+1]+p[2*i+1]) ;
if(q<0) for(i=1;2*i<n;i++) for(k=0;k<2;k++)
{ q = p[2*i+k] ; p[2*i+k] = p[2*(n-i)+k] ; p[2*(n-i)+k] = q ; }
m[mi].coords = p ;
}
// create an empty canvas with suitable attributes
c = document.createElement('canvas') ;
image.offs = { x:0 , y:0 } ;
if(imagestyle&&imagestyle.x) image.offs.x = imagestyle.x ;
if(imagestyle&&imagestyle.y) image.offs.y = imagestyle.y ;
c.setAttribute('style','width:'+image.w+'px;height:'+image.h+'px;'+
'position:absolute;left:'+image.offs.x+'px;'+
'top:'+image.offs.y+'px') ;
if(image.debug!='touch'&&!window.matchMedia("(hover: none)").matches)
{ c.onmouseover = mousefactory(image) ;
c.onmousemove = mousefactory(image) ;
c.onmouseout = leavefactory(image) ;
}
c.onclick = clickfactory(image) ;
dpr = window.devicePixelRatio ;
c.width = Math.floor(dpr*image.w) ;
c.height = Math.floor(dpr*image.h) ;
image.ovl = ctx = c.getContext('2d') ;
ctx.scale(dpr,dpr) ;
if(image.debug=='touch'||window.matchMedia("(hover: none)").matches)
if(regionstyle.touchBorderOpacity) for(i=0;i<m.length;i++)
{ p = getp(m[i]) ;
if((co=getbordercoords(m[i],p)))
drawshape(ctx,p,co,regionstyle.borderColor,regionstyle.borderWidth,
regionstyle.touchBorderOpacity) ;
}
image.div.appendChild(image.link) ;
image.canlink = document.createElement('a') ;
image.canlink.setAttribute('href',image.linkurl?image.linkurl:'#') ;
image.canlink.appendChild(c) ;
image.div.appendChild(image.canlink) ;
if(!image.linkurl) image.canlink.style.cursor = 'default' ;
return image.div ;
}
/* -------------------------------------------------------------------------- */
function addtitletomappedimage(image,titlediv)
{ image.titlediv = titlediv ; image.title = titlediv.innerHTML ; }
/* -------------------------------------------------------------------------- */
// outpoint computes the offset from p1 (the inner vertex of an anticlockwise
// polygon) to the corresponding outer vertex on its border, assuming a border
// width of 1, when p0 and p2 are the preceding and following vertices
// following https://stackoverflow.com/questions/36722826/calculate-...
// the-outer-vertices-for-a-border-of-uniform-thickness-x-drawn-around-a
function outpoint(p0,p1,p2)
{ var cotalpha , sintheta , costheta , d0 , d2 , sinalpha , cvx;
// shift coordinates to put p1 at the origin
var x0=p0.x-p1.x,y0=p0.y-p1.y,x2=p2.x-p1.x,y2=p2.y-p1.y ;
d0 = Math.sqrt(x0*x0+y0*y0) ;
d2 = Math.sqrt(x2*x2+y2*y2) ;
// 𝜽 is the angle subtended by the line p1–p2 to the x-axis;
// 𝜶 is half the additional angle needed to reach the line p1–p0
sinalpha = Math.sqrt((d2*x0-d0*x2)*(d2*x0-d0*x2)+(d2*y0-d0*y2)*(d2*y0-d0*y2)) /
(2*d0*d2) ;
if(sinalpha<1e-10) return null ;
cotalpha = Math.sqrt(1-sinalpha*sinalpha) / sinalpha ;
// this gives us the magnitude but we also need the sign
if(x0*y2>x2*y0) cotalpha = -cotalpha ;
costheta = x2 / d2 ;
sintheta = y2 / d2 ;
return { x:costheta*cotalpha-sintheta , y:costheta+sintheta*cotalpha } ;
}
/* -------------------------------------------------------------------------- */
function findregion(e,image)
{ var i , m=image.map , pt ;
if(!e||!m) return null ;
pt = [ e.pageX-image.div.offsetLeft-image.offs.x,
e.pageY-image.div.offsetTop -image.offs.y ] ;
if(image.debug==1||image.debug=='1') console.log('('+pt[0]+','+pt[1]+')') ;
for(i=0;i<m.length&&!pointinpoly(pt,m[i].coords);i++) ;
if(i<m.length) return i ; else return null ;
}
/* -------------------------------------------------------------------------- */
// based on https://gist.github.com/vlasky/d0d1d97af30af3191fc214beaf379acc
// by Vlad Lasky
function pointinpoly(point,vtx)
{ var x=point[0],y=point[1],wn,i,j,n=vtx.length/2 ;
if(vtx.length==3) // circle
return (x-vtx[0])*(x-vtx[0]) + (y-vtx[1])*(y-vtx[1]) <= vtx[2]*vtx[2] ;
if(vtx.length==5) // ellipse
return Math.sqrt((x-vtx[0])*(x-vtx[0])+(y-vtx[1])*(y-vtx[1])) +
Math.sqrt((x-vtx[2])*(x-vtx[2])+(y-vtx[3])*(y-vtx[3])) <= 2*vtx[4] ;
if(n==2) return vtx[0]<=x && x<=vtx[2] && vtx[1]<=y && y<=vtx[3] ; // rectangl
function isleft(xj,yj,xi,yi,x,y) { return (xi-xj)*(y-yj) - (x-xj)*(yi-yj) ; }
for(wn=i=0;i<n;i++)
{ j = (i?i-1:n-1) ;
if(vtx[2*j+1]<=y)
{ if(vtx[2*i+1]>y&&isleft(vtx[2*j],vtx[2*j+1],vtx[2*i],vtx[2*i+1],x,y)>0)
wn += 1 ;
}
else
{ if(vtx[2*i+1]<=y&&isleft(vtx[2*j],vtx[2*j+1],vtx[2*i],vtx[2*i+1],x,y)<0)
wn -= 1 ;
}
}
return wn != 0 ;
}
/* -------------------------------------------------------------------------- */
function getbordercoords(m,p)
{ if(m.bordercoords) return m.bordercoords ;
var c = new Array(n) , i , n=p.length , c , p0 , p1 , p2 , s;
if(n==5)
{ c[0] = (p[0]+p[2]) / 2 ;
c[1] = (p[1]+p[3]) / 2 ; // coords of centre
s = (p[1]-c[1])*(p[1]-c[1]) + (p[0]-c[0])*(p[0]-c[0]) ;
c[3] = Math.sqrt(s) ; // dist from centre to foci
if(c[3]==0) c[2] = 0 ;
else c[2] = Math.atan2(p[3]-p[1],p[2]-p[0]) ; // orientation of major axis
if(p[4]*p[4]-s<0) c = null ;
else c[4] = Math.sqrt(p[4]*p[4]-s) ; // minor axis (major is p[4])
}
else for(i=0;i<n;i+=2)
{ if(i==0) p0 = { x:p[n-2] , y:p[n-1] } ; else p0 = { x:p[i-2] , y:p[i-1] } ;
if(i==n-2) p2 = { x:p[0] , y:p[1] } ; else p2 = { x:p[i+2] , y:p[i+3] } ;
if(!(p1=outpoint(p0,{x:p[i],y:p[i+1]},p2))) { c = null ; break ; }
else { c[i] = p1.x ; c[i+1] = p1.y ; }
}
if(c) return m.bordercoords = c ;
else if(p.length==5) alert('impossible ellipse supplied for '+m.title) ;
else alert('degenerate polygon supplied for '+m.title) ;
return null ;
}
function getp(m)
{ var p = m.coords ;
if(p.length==3) return [ p[0],p[1] , p[0],p[1] , p[2] ] ;
else if(p.length==4)
return [ p[0],p[1] , p[0],p[3] , p[2],p[3] , p[2],p[1] ] ;
else return p ;
}
/* -------------------------------------------------------------------------- */
function drawshape(ctx,p,c,colour,h,opacity,imagew,imageh)
{ var i , n = p.length/2 , qx , pt ;
ctx.fillStyle = colour ;
if(opacity) ctx.globalAlpha = opacity ; else ctx.globalAlpha = 1 ;
ctx.beginPath() ;
if(!h) // draw surrounding rectangle
{ pt = getpt(p,c) ;
qx = Math.min(0,pt[0]) ;
ctx.moveTo(qx,pt[1]) ;
ctx.lineTo(qx,0) ;
ctx.lineTo(imagew,0) ;
ctx.lineTo(imagew,imageh) ;
ctx.lineTo(qx,imageh) ;
ctx.lineTo(qx,pt[1]) ;
}
else if(p.length==5) // draw outer ellipse
ctx.ellipse(c[0],c[1],p[4]+h,c[4]+h,c[2],0,2*Math.PI) ;
else // draw outer polygon
{ ctx.moveTo(p[0]+h*c[0],p[1]+h*c[1]) ;
for(i=n-1;i>=0;i--) ctx.lineTo(p[2*i]+h*c[2*i],p[2*i+1]+h*c[2*i+1]) ;
}
if(p.length==5) // draw inner ellipse
ctx.ellipse(c[0],c[1],p[4],c[4],c[2],0,2*Math.PI,1) ;
else // draw inner polygon
{ for(i=0;i<n;i++) ctx.lineTo(p[2*i],p[2*i+1]) ;
ctx.lineTo(p[0],p[1]) ; // needed?
}
ctx.fill() ;
}
/* -------------------------------------------------------------------------- */
function getpt(p,c)
{ // if the region is an ellipse, we compute the coordinates of the
// point from which we start drawing, which is a point on the minor axis
var pt = [0,0] ;
if(p.length==5)
{ if(c[3]==0) { pt[0] = 0 ; pt[1] = -c[4] ; }
else
{ pt[0] = - c[4] * (p[3]-p[1]) / (2*c[3]) ;
pt[1] = c[4] * (p[2]-p[0]) / (2*c[3]) ;
}
if(pt[0]>0) { pt[0] = -pt[0] ; pt[1] = -pt[1] ; }
pt[0] += c[0] ;
pt[1] += c[1] ;
}
else { pt[0] = p[0] ; pt[1] = p[1] ; }
return pt ;
}
/* -------------------------------------------------------------------------- */
function imagetwitch(e,image,bordercolour,h,outfillcolour,outfillopacity)
{ var i , m=image.map , p , c , pt , ctx = image.ovl ;
if(image.region==undefined) image.region = null ;
// --- vvv nested function
function setlink(title,linkurl)
{ if(image.titlediv)
{ while(image.titlediv.lastChild)
image.titlediv.removeChild(image.titlediv.lastChild) ;
image.titlediv.innerHTML = title ;
}
image.link.setAttribute('href',linkurl?linkurl:'#') ;
image.canlink.setAttribute('href',linkurl?linkurl:'#') ;
if(linkurl)
{ image.link.style.cursor = image.canlink.style.cursor = 'pointer' ;
if(!image.titlediv) image.link.title = image.canlink.title = title ;
}
else
{ image.link.style.cursor = image.canlink.style.cursor = 'default' ;
image.link.removeAttribute(title) ;
image.canlink.removeAttribute(title) ;
}
}
// --- ^^^ nested function
i = findregion(e,image) ;
if(i==image.region) return ; else image.region = i ;
if(i==null)
{ ctx.clearRect(0,0,image.w,image.h) ;
setlink(image.title,image.linkurl) ;
return ;
}
m = m[image.region] ;
ctx.clearRect(0,0,image.w,image.h) ;
setlink(m.title,m.linkurl) ;
if((!bordercolour||!h)&&(!outfillcolour||outfillopacity==0)) return ;
p = getp(m) ;
if(!(c=getbordercoords(m,p))) return ;
if(outfillcolour&&outfillopacity!=0)
{ if(!outfillopacity) outfillopacity = 1 ;
drawshape(ctx,p,c,outfillcolour,0,outfillopacity,image.w,image.h) ;
}
if(bordercolour&&h) drawshape(ctx,p,c,bordercolour,h,0) ;
}