Create an Image Montage and a Corresponding HTML Area Map
As noted elsewhere Linux Journal turned 15 this month. Hopefully, you enjoyed viewing all our old covers. In the pursuit of the best way to display those covers my first prototype was rejected. Take a look at what didn't make the cut and read on to find out how it was created.
If you haven't looked, the idea was to create a single image where all the covers were reduced to thumbnail size and displayed in a grid where each row in the grid would be a year's worth of covers. Further the image itself would serve as an HTML <map>, each cover being an <area>. As you moved the mouse over each thumbnail the full size cover would be displayed elsewhere on the page.
The main thing that I wasn't quite sure how to accomplish at the get go was creating the thumbnails and pasting them together. I certainly wasn't going to do it manually, that's what programs are for.
My first thought was to use ImageMagick and PythonMagick but the rather poor ImageMagick API documentation convinced me otherwise. Thought two was PIL, the Python Imaging Library.
The entire program is attached and is obviously going to require some modifications for use in other contexts but below is the meat of the program:
85 montage_width = (THUMBNAIL_WIDTH * THUMBNAIL_COLUMNS) + YEAR_WIDTH
86 montage_height = THUMBNAIL_HEIGHT * THUMBNAIL_ROWS
87 montage_img = Image.new('RGB', (montage_width, montage_height))
88 montage_draw = ImageDraw.Draw(montage_img)
89
90 print 'Thumbnails: %dx%d' % (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
91 print 'Cols Rows : %dx%d' % (THUMBNAIL_COLUMNS, THUMBNAIL_ROWS)
92 print 'Size : %dx%d' % (montage_width, montage_height)
93
94 img_tag = '<img src="%s" width="%d" height="%d" alt="%s" border="0" usemap="#%s" />\n'
95 map_tag = '<map id="%s" name="%s">\n'
96 area_tag = ' <area shape="rect" ' \
97 'coords="%d,%d,%d,%d" ' \
98 'href="%s" ' \
99 'onmouseover="javascript:cmap_show_image(\'%s\', \'%s\', %d, \'%s\');"/>\n'
100
101 # Create html file.
102 html_output = open(MONTAGE_HTML, 'w')
103
104 # Insert javascript.
105 html_output.write('<script language="JavaScript">\n');
106 html_output.write(open(MONTAGE_JS).read() % (len(covers), image_basename))
107 html_output.write('</script>\n\n');
108
109 # Add image tag for cover montage.
110 html_output.write('<table border="0"><tr><td>\n')
111 html_output.write(img_tag % (montage_url, montage_width, montage_height,
112 MONTAGE_IMG_ALT, MONTAGE_MAP_ID))
113 html_output.write(map_tag % (MONTAGE_MAP_ID, MONTAGE_MAP_ID))
114
115 year = 1994
116 column = FIRST_ROW_BLANKS
117 xpos = YEAR_WIDTH + (THUMBNAIL_WIDTH * column)
118 ypos = 0
119 for ix, cover in enumerate(covers):
120 inum = ix + 1
121
122 # Load cover image, resize it and paste it into the montage.
123 img = Image.open(cover)
124 img = img.resize((THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT))
125 xpos2 = xpos + THUMBNAIL_WIDTH
126 ypos2 = ypos + THUMBNAIL_HEIGHT
127 box = (xpos, ypos, xpos2, ypos2)
128 montage_img.paste(img, box)
129
130 # Add area tag for this image to the map.
131 imonth, iyear = convert_issue_to_month_year(inum)
132 ititle = '#%d - %s, %s' % (inum, imonth, iyear)
133 ilink = AREA_HREF % inum
134 html_output.write(area_tag % (xpos, ypos, xpos2, ypos2,
135 ilink,
136 COVER_IMG_ID, MONTAGE_MAP_ID, inum, ititle))
137
138 column += 1
139 if ix == 1 or ix == 2:
140 xpos += THUMBNAIL_WIDTH
141 column += 1
142
143 if column == THUMBNAIL_COLUMNS or ix == len(covers)-1:
144 # Add the row label to the montage.
145 if YEAR_WIDTH > 0:
146 ystr = '%d' % year
147 ysz = montage_draw.textsize(ystr)
148 h = ysz[1]
149 y = ypos + ((THUMBNAIL_HEIGHT - h + h // 2) // 2)
150 montage_draw.text((4, y), ystr)
151 year += 1
152
153 xpos = YEAR_WIDTH
154 ypos += THUMBNAIL_HEIGHT
155 column = 0
156 else:
157 xpos += THUMBNAIL_WIDTH
158
159 montage_img.save(MONTAGE_JPG, 'JPEG')
160
161
162 # Finish html.
163 html_output.write('</map>\n')
164
165 html_output.write('</td>\n')
166 html_output.write('<td valign="center">\n')
167 img_tag = '<img src="%s%03d.jpg" id="%s" border="0" alt="%s" />\n'
168 html_output.write(img_tag % (image_basename, len(covers) , COVER_IMG_ID, MONTAGE_IMG_ALT))
169 html_output.write('</tr></table>\n')
170
171 html_output.close()
A few lines in Image.new() is called to create a montage image of the size needed to hold all the thumbnails and an extra column for the year. Then a drawing surface on the image is created.
After that a file is opened for containing the generated HTML. The first thing added to the file is the JavaScript (see below) that provides the onmouseover handling for the <area> tags. The JavaScript file contains a couple of parameters (the number of images and the URL to the images) which are filled in during the copy process. Having the Python program fill in the URL makes for easier testing where the local URL may be different than the live URL.
Next the <table> tag is written and the montage image and the <map> tag are added to the first column. The montage image is shown in one column and the full size cover is shown in the second column. Then the program begins iterating over all the cover images.
Each cover image is read, resized, and pasted into the montage. The coordinates for pasting the image into the montage are the same ones that are used for the corresponding <area> tag so that is then added to the HTML output file. Then comes some funny business related to skipping some spots in the montage (during our first year we had a couple of bi-monthly issues). After that a check is done to see if we are in a new row and if so we write the year into the first column of the montage image using PIL.ImageDraw.text()
When all the covers have been processed the montage image is saved to a file. The <map> is closed and the second column of the table is written with an <img> tag for the full sized cover. Then the <table> is closed and the HTML is done.
The JavaScript for handling the onmouseover event is presented below:
// The percent format fields are filled in by cmap.py
var cmap_n_images = %d;
var cmap_image_base = '%s';
var cmap_image_urls = Array();
var cmap_image_objs = Array();
function cmap_show_image(imgid, mapid, inum, ititle)
{
var img = document.getElementById(imgid);
var map = document.getElementById(mapid);
var i;
var j;
if ( img && map ) {
if ( cmap_image_urls.length == 0 ) {
// Create image urls and image objects.
for ( i = 0; i < cmap_n_images; i++ ) {
j = i + 1;
if ( j < 10 ) zero = '00';
else if ( j < 100 ) zero = '0';
else zero = '';
cmap_image_urls[i] = cmap_image_base + zero + j + '.jpg';
cmap_image_objs[i] = new Image();
}
}
i = inum - 1;
if ( !cmap_image_objs[i].src ) {
cmap_image_objs[i].src = cmap_image_urls[i];
}
img.src = cmap_image_objs[i].src;
map.title = ititle
}
}
Its operation is fairly simple, the cmap_show_image() function is called each time the mouse moves over an <area> tag. The function is called with the id of the full size image tag, the id of the map, the issue number, and the issue title.
On the first pass through the function creates Image objects for all the cover images and generates the URLs for all the images. The Image objects are created without specifying the src attribute, so they are empty images at this point.
The function then checks to see if the cover image for the issue specified as a parameter has its src attribute set, if not it sets it which will cause the image to be loaded. By loading the images only when the mouse moves over an area in the map all the images don't need to be loaded upfront.
At the end of the function, the image object's src attribute is copied to the full size cover image so that the full size cover gets displayed in the second column. The last action is to set the map title to the title of the issue.
| Attachment | Size |
|---|---|
| cmap.zip | 2.54 KB |
Mitch Frazier is an Associate Editor for Linux Journal.
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Sponsored by AMD
If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.
Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.
Sponsored by ActiveState
| Speed Up Your Web Site with Varnish | Jun 19, 2013 |
| Non-Linux FOSS: libnotify, OS X Style | Jun 18, 2013 |
| Containers—Not Virtual Machines—Are the Future Cloud | Jun 17, 2013 |
| Lock-Free Multi-Producer Multi-Consumer Queue on Ring Buffer | Jun 12, 2013 |
| Weechat, Irssi's Little Brother | Jun 11, 2013 |
| One Tail Just Isn't Enough | Jun 07, 2013 |
- Speed Up Your Web Site with Varnish
- Containers—Not Virtual Machines—Are the Future Cloud
- Linux Systems Administrator
- Lock-Free Multi-Producer Multi-Consumer Queue on Ring Buffer
- Senior Perl Developer
- Technical Support Rep
- Non-Linux FOSS: libnotify, OS X Style
- UX Designer
- Web & UI Developer (JavaScript & j Query)
- RSS Feeds
Featured Jobs
| Linux Systems Administrator | Houston and Austin, Texas | Host Gator |
| Senior Perl Developer | Austin, Texas | Host Gator |
| Technical Support Rep | Houston and Austin, Texas | Host Gator |
| UX Designer | Austin, Texas | Host Gator |
| Web & UI Developer (JavaScript & j Query) | Austin, Texas | Host Gator |
Free Webinar: Hadoop
How to Build an Optimal Hadoop Cluster to Store and Maintain Unlimited Amounts of Data Using Microservers
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Some of key questions to be discussed are:
- What is the “typical” Hadoop cluster and what should be installed on the different machine types?
- Why should you consider the typical workload patterns when making your hardware decisions?
- Are all microservers created equal for Hadoop deployments?
- How do I plan for expansion if I require more compute, memory, storage or networking?



21 min 13 sec ago
2 hours 47 min ago
6 hours 46 min ago
8 hours 3 min ago
11 hours 34 min ago
14 hours 28 min ago
14 hours 53 min ago
17 hours 22 min ago
17 hours 55 min ago
17 hours 56 min ago