创建图像蒙太奇和相应的 HTML 区域地图

作者:Mitch Frazier

正如其他地方 Linux Journal 指出,本月迎来了创刊 15 周年。希望您喜欢浏览我们所有的旧封面。为了找到展示这些封面的最佳方式,我的第一个原型被否决了。看看这个未被采纳的版本,并继续阅读以了解它是如何创建的。

如果您还没有看过,这个想法是创建一个单一图像,其中所有封面都被缩小为缩略图大小,并以网格形式显示,网格中的每一行代表一年的封面。此外,图像本身将用作 HTML<map>,每个封面都是一个<area>。当您将鼠标移动到每个缩略图上时,完整尺寸的封面将显示在页面的其他位置。

我一开始不太确定如何完成的主要事情是创建缩略图并将它们粘贴在一起。我当然不会手动完成,那是程序的作用。

我的第一个想法是使用 ImageMagickPythonMagick,但是相当糟糕的 ImageMagick API 文档让我放弃了这个想法。第二个想法是 PIL,即 Python 图像库。

整个程序都附在后面,显然需要进行一些修改才能在其他环境中使用,但下面是程序的核心部分

 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()

Image.new() 中的几行代码被调用来创建一个 montage 图像,其大小足以容纳所有缩略图以及用于年份的额外列。然后在图像上创建一个绘图表面。

之后,打开一个文件以包含生成的 HTML。添加到文件的第一件事是 JavaScript (见下文),它提供了onmouseover处理程序,用于<area><area> 标签。JavaScript 文件包含几个参数(图像的数量和图像的 URL),这些参数在复制过程中被填充。让 Python 程序填写 URL 可以更轻松地进行测试,因为本地 URL 可能与实际 URL 不同。

接下来,<table>标签被写入,蒙太奇图像和<map><map> 标签被添加到第一列。蒙太奇图像显示在一列中,完整尺寸的封面显示在第二列中。然后程序开始迭代所有封面图像。

每个封面图像都被读取、调整大小并粘贴到蒙太奇中。将图像粘贴到蒙太奇中的坐标与相应的<area><area> 标签使用的坐标相同,因此该标签也被添加到 HTML 输出文件中。接下来是一些与跳过蒙太奇中的某些位置相关的有趣操作(在我们创刊的第一年,我们有几期双月刊)。之后,检查是否是行,如果是,则使用PIL.ImageDraw.text()

将年份写入蒙太奇图像的第一列。<map>当所有封面都处理完毕后,蒙太奇图像将保存到文件中。<map> 被关闭,表的第二列被写入一个<img><table>标签,用于完整尺寸的封面。然后

<table> 被关闭,HTML 就完成了。onmouseover用于处理

// 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
    }
}

事件的 JavaScript 如下所示它的操作非常简单,每次鼠标移动到cmap_show_image()<area><area> 标签上时,

function is called each time the mouse moves over an

函数都会被调用。调用该函数时会传递完整尺寸图像标签的id、地图的id<map>、期号和期刊名

在第一次通过该函数时,会为所有封面图像创建Image对象,并生成所有图像的 URL。Image对象在创建时没有指定src属性,因此它们此时是空图像。

然后,该函数检查作为参数指定的期号的封面图像是否已设置其srcsrc

属性,如果未设置,则设置该属性,这将导致图像被加载。通过仅在鼠标移动到地图中的区域上时才加载图像,所有图像都不需要预先加载。src在函数结束时,图像对象的

src

属性被复制到完整尺寸的封面图像,以便完整尺寸的封面显示在第二列中。最后一个操作是将地图标题设置为期刊的标题。