graphein: Scaling

1. Scaling

1.1. Resolution Issues and Proposed Resolution

The resolution of screens keeps increasing, and is not presently (2006) anywhere near the limits of human visual acuity. The resolution of print media is already at those limits for high-end printing, and at a significant fraction of them for everyday printing. So any document must exist in an environment of potentially conflicting requirements, where it may be viewed on a low-resoultion screen, a high-resolution screen (still not near the limits of visual acuity, though), reasonably high-level print, or quite high-level print.

Bitmapped images, on the other hand, are of fixed size. This presents an obvious difficulty.

My solution is to generate multiple versions of the documents, with their bitmapped images scaled variously, so that the reader has a choice of versions and some chance, at least, that one of those choices might be satisfactory.

This in turn implies some idea of what scalings are appropriate. Bitmapped images themselves have dimensions in pixels, not in physical lengths. Correlations with physical sizes are external to the image (even if the image, usually misleadingly, also encodes a "resolution"). Somewhat arbitrarily, I'll take the smallest size image of interest to be one that is 64 pixels wide. If displayed such that this 64 pixel wide image is 10mm wide (on-screen, say), this gives a not unreasonable resolution (for a screen, that is) of a bit over 6 px/mm. On a computer screen of 2006, this might well represent a smallish but not tiny icon. I'll proceed up in familiar (to a programmer) powers of two: 128 pixels wide, 256, 512, and 1024. A 1024 pixel wide version of this same smallish icon would nearly fill a computer screen nowdays (again, 2006), but in the presumably better screens of the future might once again become a smallish icon. (The history of the computer is to a very great extent a history of "nah, it'll never get that big...")

1024 pixels displayed in 10mm also corresponds fairly well to the common rule of thumb for the limits of visual acuity: 100 pixels / mm. (This is familiar to photographers as 100 lines/mm, and to US users of high-end printing equipment as 2,540 dpi.)

Here, then are five "Scale Factors" ("sfs"):

Scale Factor 0: (2^0 = 1) * 64 = 64 smallest useful (6.5 px/mm if 10mm)
Scale Factor 1: (2^1 = 2) * 64 = 128
Scale Factor 2: (2^2 = 4) * 64 = 256
Scale Factor 3: (2^3 = 8) * 64 = 512
Scale Factor 4: (2^4 = 16) * 64 = 1024 near visual acuity (102.4 px/mm if 10mm)

Each of these "Scale Factors" represents the width in pixels of an image that is supposed to be (very roughly!) 10mm wide on the output medium. For example, the icons used to identify the major sections of the CircuitousRoot.com website are supposed to be about 20mm wide. At Scale Factor 0, they'd therefore be 2 * 64 = 128 pixels wide; at sf1, 2 * 128 = 256, etc., up to sf4: 2 * 1,024 = 2,048 pixels wide. On computer screens of 2006, sf1 or sf2 would be appropriate.

Here are a series of bitmapped images each of which is a square at "1x" in each scale factor. Their physical size on your screen will depend on your screen. The one which is closest to 10mm square on your screen is the one at the "scale factor" appropriate for your screen.

1x Square at sf0 (64x64 pixels)

1x Square at sf0 (64x64 pixels)

(I find that I'm being a trifle optimistic here. On the screen on which I'm writing this, a pretty standard 2004 vintage LCD display, the above is about 17mm square.)

1x Square at sf1 (128 x 128 pixels)

1x Square at sf1 (128 x 128 pixels)

1x Square at sf2 (256 x 256 pixels)

1x Square at sf2 (256 x 256 pixels)

1x Square at sf3 (512 x 512 pixels)

1x Square at sf3 (512 x 512 pixels)

1x Square at sf4 (1,024 x 1,024 pixels)

1x Square at sf4 (1,024 x 1,024 pixels)

Won't it be nice when we all have computer screens such that the last image above is only 10mm wide...

Each image to be scaled should be prepared in a "master" version intended to be one of these scaling increments. Ideally, each would be mastered at Scale No. 4, the maximum, so that only scaling-down would occur. In practice, this may not be possible, either because the image is not available at that resolution or because it would take too much space. If this is the case, then scaling up (with consequent visual imperfections) must occur.

Image masters should encode their Scale Factor in their filename, as the last three characters before the suffix. Thus: icon-sample-sf2.png or image-sample-sf4.png

Remember, sf is not image width, but rather the density of pixels in about 10mm of an image. A 512 pixel wide image intended for use as a (2 * sf) wide icon would be sf2 (2 * 256 = 512): icon-sample-sf2.png. A 51,200 pixel wide image intended to show up as, oh, about 50mm wide would be sf3 (50 * 1,024 = 51,200): image-sample-sf4.png (that's a big bitmap file, at least in 2006). In other words, divide the image width in pixels (say, 51,200) by the sf in pixels (sf4 = 1,024) to get the apparent width in mm. Or, divide the image width in pixels (51,200) by the desired apparent width in mm (50) to get (one hopes) the sf in pixels (1,024).

1.2. Screen vs. Page Output

I'll distinguish two general categories of output: computer screens intended as such, and "page" output whether on physical paper or on screen (in PDF, for example).

For screen output, the make process should generate multiple versions of the scalable images. The highest-resolution images generated may not go all the way to sf4, as they'd be too big. (In 2006, I max out at sf2.) The masters, however, should be at the highest sf possible (the masters aren't published, only the scaled versions, so the cost here is only local storage space, not server space or bandwidth). I'll hardcode this limit in the makefile.

Each page will have, automatically generated, a choice at its bottom which allows the reader to change resolutions. Movement from page to page should, otherwise, stay at the current rsolution. This implies multiple copies of (the HTML of) each page as well: one for each sf.

For page output, I'll generate different versions and tend toward higher sfs. PDFs online might have low sf images so as to keep their size down. PDFs for ordinary printing might have medium sf images. PDFs for high-end printing, should such ever occur, would of course be sf4.

The page output more tightly integrates the images (they're part of the PDF, for example).

1.3. Two Categories of Bitmapped Images

I'll distinguish two types of bitmapped images:

Images to be scaled include the standard "Home" and "Up" linking icons, of course. They also probably (but not necessarily) include other linking icons (e.g., to subsections). They may also include any other images in the document.

Some images shouldn't be scaled at all. This might be the case, for example, with a historical digital image at a low resolution which would lose integrity if scaled, or with an image where a certain fine feature is important (and should not be lost by accident as an artefact of the scaling process). Such an image will usually appear too small or too large, but that is unavoidable.

(It might be possible to specify a minimum intended width and then scale only up, so as never to lose pixels but only to duplicate them. I'll leave that sophistication, if indeed it is sophisticated, for later.)

1.4. Images Directories

An original version of each image file should be kept in the ./image-development subdirectory of the directory in which it is introduced. (This need not be the absolute original, which might, for example, be a huge scan. It should, though, be original enough that all future image development work for the document may be done with it.) All editing of this image to generate scaling masters (and whatever else) should also occur in this ./image-development subdirectory. The contents of this subdirectory are not intended to be published. (It can thus contain embarassingly unfinished work and material too large for web server disk and bandwidth limitations.)

Each image which is to be scalable should have a "master" for scaling made, by hand, in ./image-development. This image file is then either copied to ./images-mastered-for-scaling or, better, a symbolic link to it should be made in ./images-mastered-for-scaling.

Image scaling masters should specify their sfs. If icons are to come out at standard widths (e.g., I often use 2x sf) then they ought to be integral multiples of the sfs in pixels. For other images, the sf of the scaling master indicates approximate apparent width in mm (see "Resolution Issues and Proposed Resolution," above).

Note that the special linking icons " link-home.png" and " link-to-me.png" exist in ./images-mastered-for-scaling only when they are new. The special linking icon " link-up.png" is mastered nowhere; it is entirely transient.

Non-scalable images are developed as usual in ./image-development, and then simply copied into the main directory alongside the TEI and other document files there.

1.5. Scaling the Images

The scaled images are transient dependencies created in the make process. They are triggered by a fake dependency, images-scaled.dep

<<target-dependencies>>= 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 

Since there is already a mechanism for creating complex included dependencies dynamically (the dependencies.dep target, discussed later), the code for creating these image dependencies is put in the script for dependencies.dep, generate-dependencies.sh. The portion relevant for the dependencies.dep target is:

<<generate-image-dependencies>>= 
currentsf=0 
echo -n "images-scaled.dep: " 
while [ $currentsf -le $1 ]; do 
for image in `ls *-sf?.png`; do 
echo "\\" 
echo -n "`echo $image | awk '{ print substr($1,0,index($1,".")-2) }'`$currentsf.jpg" 
done 
currentsf=`expr $currentsf + 1` 
done 
echo 
echo -n " touch images-scaled.dep" 
echo 
echo 

This might generate, for example, the make rule:

 
images-scaled.dep:\ 
link-home-sf0.jpg\ 
link-to-lemur-dot-com-sf0.jpg\ 
link-to-me-sf0.jpg\ 
test-sf0.jpg\ 
link-home-sf1.jpg\ 
link-to-lemur-dot-com-sf1.jpg\ 
link-to-me-sf1.jpg\ 
test-sf1.jpg\ 
link-home-sf2.jpg\ 
link-to-lemur-dot-com-sf2.jpg\ 
link-to-me-sf2.jpg\ 
test-sf2.jpg 
touch images-scaled.dep 

The actual code to generate each image is more complex:

<<scale-images>>= 
currentsf=0 
while [ $currentsf -le $1 ]; do 
for image in `ls *-sf?.png`; do 
mastersf=`echo $image | awk '{ print substr($1,index($1,".")-1,1) }'` 
echo "`echo $image | awk '{ print substr($1,0,index($1,".")-2) }'`$currentsf.jpg: 
images-mastered-for-scaling/$image" 
echo " (cd images-mastered-for-scaling; \\" 
echo " pwd; \\" 
echo " cp -f $image trash.png; \\" 
echo " convert trash.png -resize `echo "2^($currentsf-$mastersf) * 100" | bc -l`% 
`echo $image | awk '{ print substr($1,0,index($1,".")-2) }'`$currentsf.jpg; \\" 
echo " rm -f trash.png; \\" 
echo " mv -f `echo $image | awk '{ print substr($1,0,index($1,".")-2) }'`$currentsf.jpg 
..; \\" 
echo " cd .. )" 
echo 
echo 
echo "images-mastered-for-scaling/$image:" 
echo 
done; 
currentsf=`expr $currentsf + 1` 
done 

This script will loop through all desired sfs (the maximum sf is passed to the generate-dependencies.sh script from make, where it is hardcoded). At each sf, for each image scaling master, it generates a make, rule to create the scaled image. There are only two tricky bits to this.

First, there are a couple of examples of using Awk language scripts to do string manipulation of filenames. The sed stream editor might have been more traditional here, but I know Awk better. I'll leave their explanation to any introductory Awk textbook. Remember that back-single-quotes tell the shell to execute their contents as a command.

Second, there is an interesting use of the bc calculator to do arithmetic. The ordinary expr arithmetic wasn't sufficient, as exponentiation was required. (negative exponents give the scalings-down).

Worked out, the formula results in:

 
Formula for scale width: 2^N * 100 
where N is (currentsf) - (image master sf) 
Example (master sf as big as possible, at sf4) 
0 - 4 = -4, so 2^-4 * 100 = 6.25 % 
1 - 4 = -3, so 2^-3 * 100 = 12.5 % 
2 - 4 = -2, so 2^-2 * 100 = 25 % 
3 - 4 = -1, so 2^-1 * 100 = 50 % 
4 - 4 = 0, so 2^0 * 100 = 100 % 
(note: doing a 100% convert is inefficient, 
but not incorrect) 
Example (master sf in middle at sf2) 
0 - 2 = -2, so 2^-2 * 100 = 25 % 
1 - 2 = -1, so 2^-1 * 100 = 50 % 
2 - 2 = 0, so 2^0 * 100 = 100 % 
3 - 2 = 1, so 2^1 * 100 = 200 % 
4 - 2 = 2, so 2^2 * 100 = 400 % 

All of this is made slightly more complex as it is done inside echo commands which are generating second-level bash code for ultimate execution during make as make re-makes the included dependencies.dep material. Note that the expressions in bc are executed in the generate-dependencies.sh script during its execution by make to create the rules and scripts then included in the same make file and process and in turn executed by (the same) make . Wheels within wheels.