home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.cs.arizona.edu
/
ftp.cs.arizona.edu.tar
/
ftp.cs.arizona.edu
/
icon
/
historic
/
v941.tgz
/
icon.v941src.tar
/
icon.v941src
/
ipl
/
gprogs
/
gallery.icn
< prev
next >
Wrap
Text File
|
2001-05-29
|
15KB
|
573 lines
############################################################################
#
# File: gallery.icn
#
# Subject: Program to display many images at once
#
# Author: Gregg M. Townsend
#
# Date: May 29, 2001
#
############################################################################
#
# This file is in the public domain.
#
############################################################################
#
# Usage: gallery [-{gc}n] [-{whs}nnn] [-{rmtud}] file...
#
# Gallery displays multiple images in a single window. The images
# are shrunken by resampling and tiled in columns or rows.
#
# GIF and XPM format images are always supported. JPEG format is
# supported when built by Jcon. Raw-mode PPM files are supported
# if the v9 loadfunc() library is available. JPEG, PPM, and RLE
# formats are also available under Unix if the necessary conversion
# utilities are available in the shell search path.
#
# When the window fills, diagonal lines in the extreme corners of the
# window indicate that you can press Enter for the next screenful.
# Solid triangles appear when there are no more images; press Q to exit.
#
# At either of those pauses, the clicking the left mouse button on an
# image displays a popup window with information about the image. A
# second click dismisses the popup, as does the space bar or Enter key.
# The right mouse button activates the same popup momentarily until
# the button is released.
#
# -cn or -gn selects the color palette used; -c6 is the default.
# (However, -g30 may look better even for color images.)
# The color palette is ignored when built by Jcon.
#
# -wnnn sets the maximum width for displaying an image;
# -hnnn sets the maximum height. -snnn sets both.
# By default, sizes are chosen automatically, subject to a minimum
# size of 32x32, to allow all images to fit in a single window.
#
# -r arranges images in rows instead of columns.
# -m maximizes the window size before displaying images.
# -t trims file names of leading path components and extensions.
# -u shows images completely unlabeled.
# -d prints some debugging information.
#
############################################################################
#
# Requires: Version 9 graphics
#
############################################################################
#
# Links: graphics, imscolor, options, io, random, cfunc
#
############################################################################
# TO DO:
#
# improve prompts -- something more obvious & intuitive
link graphics
link imscolor
link options
link io
link random
$undef _DYNAMIC_LOADING # delete this to enable direct PPM reading
$ifdef _DYNAMIC_LOADING
link cfunc
$endif
$define DefPalette "c6" # default palette
$define Gap 4 # gap between images
$define MinWidth 32 # minimum width if auto-scaled
$define MinHeight 32 # minimum height if auto-scaled
record area(fname, x, y, w, h, iw, ih)
global opts # command options
global tempname # temporary file name
global pal # selected palette (if any)
global ww, wh, fh, fw # window dimensions
global maxw, maxh # maximum size of displayed image
global areas # areas used for display
procedure main(args)
local cw, ch, bigh, bigw, x, y, w, h, gg, aspr, aspmax, horz
local fname, label, f, tw, s, nchars, nlines, imwin, e
# generate a random name for the temporary file
randomize()
tempname := "/tmp/gal" || right(?99999, 5, "0") || ".tmp"
# open the window and process options
Window("size=800,500", "bg=pale gray", "font=sans,8", args)
opts := options(args, "g+c+w+h+s+rmtud")
if \opts["m"] then
WAttrib("canvas=maximal")
if *args = 0 then
stop("usage: ", &progname, " [-{gc}n] [-{whd}nnn] [-{mtv}] file...")
# allow user resizing of window
&error := 1
WAttrib("resize=on")
&error := 0
# record window dimensions
ww := WAttrib("width")
wh := WAttrib("height")
if \opts["u"] then
fh := 0
else
fh := WAttrib("fheight")
fw := WAttrib("fwidth")
maxw := \opts["w"] | \opts["s"] | 2 * \opts["h"]
maxh := \opts["h"] | \opts["s"] | 2 * \opts["w"]
# If no image size specified, try to guess to fill the window
if /maxw then
layout(*args)
aspmax := real(maxw) / real(maxh)
pal := ("c" || \opts["c"]) | ("g" || \opts["g"]) | DefPalette
PaletteChars(pal) | stop("invalid palette ", pal)
# Display the files.
x := y := Gap
bigw := bigh := 0
areas := list()
every fname := !args do {
close(\f)
close(\imwin)
f := imwin := &null
# Check for an interrupt
while *Pending() > 0 do
if Event() === QuitEvents() then
return
# Get the next file and translate its image.
f := open(fname) |
{ write(&errout, fname, ": can't open"); next }
# Read the image, full sized, into a scratch canvas
if not (imwin := rdimage(fname, f)) then
{ write(&errout, fname, ": can't decode"); next }
# Scale the image to the desired size
w := WAttrib(imwin, "width")
h := WAttrib(imwin, "height")
aspr := real(w) / real(h)
if w > maxw | h > maxh then {
if aspr > aspmax then {
w := maxw
h := maxw / aspr
}
else {
w := maxh * aspr
h := maxh
}
w <:= 1
h <:= 1
Zoom(imwin, , , , , , , w, h)
}
# Trim the file name if so requested.
if \opts["t"] then
fname ? {
while tab(upto('/') + 1)
="cache"
label := tab(upto('.') | 0)
}
else
label := fname
# Calculate the area needed for display
cw := w # cell width
if /opts["u"] then
cw <:= TextWidth(label) # ensure room for label
ch := h + fh # cell height
# Place the new image on a new row or new window if needed.
if x + cw > ww | y + ch > wh then { # if row or column is full
if /opts["r"] then {
x +:= bigw + Gap # start new column
y := Gap
bigw := 0
}
else {
x := Gap # start new row
y +:= bigh + Gap
bigh := 0
}
if x + cw > ww | y + ch > wh then {
# no room for new row or column
pause() # wait for OK
EraseArea() # clear the window
ww := WAttrib("width")
wh := WAttrib("height")
x := y := Gap
bigw := bigh := 0
areas := list()
}
}
# Draw the image and its label.
CopyArea(imwin, &window, 0, 0, w, h, x, y)
if /opts["u"] then
DrawString(x, y + h + fh - WAttrib("descent"), label)
# Record the space it occupies
put(areas, area(fname, x - Gap / 2, y - Gap / 2, w + Gap, h + fh + Gap,
WAttrib(imwin, "width"), WAttrib(imwin, "height")))
# Move on to next position.
if /opts["r"] then
y +:= ch + Gap
else
x +:= cw + Gap
bigh <:= ch
bigw <:= cw
}
# All images have been displayed. Wait for "q" before exiting.
close(\f)
close(\imwin)
w := WAttrib("width")
h := WAttrib("height")
gg := 2 * Gap - 1
FillPolygon(0, 0, 0, gg - 1, gg - 1, 0)
FillPolygon(0, h, 0, h - gg, gg - 1, h - 1)
FillPolygon(w, 0, w - gg, 0, w - 1, gg - 1)
FillPolygon(w, h, w - gg, h - 1, w - 1, h - gg)
while e := Event() do case e of { # wait for event
QuitEvents(): exit() # quit on "q" etc
&lpress | &rpress: info(e) # display info about image
}
end
# layout(n) -- calculate layout for n images
$define GuessAspect 1.5 # aspect ratio guess used for layout
procedure layout(n)
local aspf, nhigh, nwide
aspf := real(ww) / real(wh) / GuessAspect
nhigh := integer(sqrt(n / aspf) + 0.5)
nhigh <:= 1
nwide := (n + nhigh - 1) / nhigh
maxw := ((ww - Gap) / nwide) - Gap
maxh := ((wh - Gap) / nhigh) - Gap - fh
maxw <:= MinWidth
maxh <:= MinHeight
if \opts["d"] then
write(&errout, "npix=", n, " aspf=", aspf, " nhigh=", nhigh,
" nwide=", nwide, " maxh=", maxh, " maxw=", maxw)
return
end
## pause() -- wait for clearance to start a new window
procedure pause()
local w, h, gg, e
while *Pending() > 0 do # consume and ignore older events
Event()
w := WAttrib("width")
h := WAttrib("height")
gg := 2 * Gap - 1
DrawLine(0, gg - 1, gg - 1, 0) # draw diagonals to indicate pause
DrawLine(0, h - gg, gg - 1, h - 1)
DrawLine(w - gg, 0, w - 1, gg - 1)
DrawLine(w - gg, h - 1, w - 1, h - gg)
while e := Event() do case e of { # wait for event
QuitEvents(): exit() # quit on "q" etc
!" \t\r\n": break # continue on "\r" etc
&lpress | &rpress: info(e) # display info about image
}
return
end
## info(event) -- display info about image under the mouse
$define InfoMargin 10 # margin around image
$define InfoHeight 80 # text area height
$define InfoWidth 300 # text area width
procedure info(e)
local a, w, h, wmin, wmax, hmax
wmin := InfoWidth + 2 * InfoMargin
wmax := WAttrib("width") - 4 * InfoMargin
hmax := WAttrib("height") - 5 * InfoMargin - InfoHeight
every a := !areas do
if InBounds(a.x, a.y, a.w, a.h) then {
w := a.iw
h := a.ih
if w >:= wmax then
h := a.ih * w / a.iw
if h >:= hmax then
w := a.iw * h / a.ih
wmin <:= w + 2 * InfoMargin
Popup(, , wmin, h + InfoHeight + 3 * InfoMargin, popinfo, a, e, w, h)
break
}
return
end
## popinfo(area, event, w, h) -- display info in the popup
#
# if event was &rpress, wait for &rrelease
# otherwise wait for &lpress, Enter, or space to dismiss
procedure popinfo(a, e, w, h)
local f, i, n, x, y
f := open(a.fname)
seek(f, 0)
n := where(f)
seek(f, 1)
i := rdimage(a.fname, f) | fail
x := (WAttrib("clipw") - w) / 2
y := InfoMargin
Zoom(i, &window, , , , , x, y, w, h)
Font("sans,bold,12")
WAttrib("leading=16")
GotoXY(0, InfoMargin + h + InfoMargin + WAttrib("ascent"))
WWrite(" ", a.fname)
WWrite(" ", a.iw, " x ", a.ih, " pixels")
WWrite(" ", n, " bytes")
WWrite(" ", iformat(f), " format")
if e === &rpress then
until Event() === &rrelease # dismiss upon button release
else {
until Event() === &lrelease # consume matching release
until Event() === &lrelease | !" \n\r" # wait for dismissal
}
WClose(i)
return
end
## rdimage(fname, f) -- read image into scratch window
procedure rdimage(fname, f)
return case iformat(f) of {
"GIF": gifread(f, fname, pal)
"JPEG": jpegread(f, fname, pal)
"PBM" | "PGM": pnmread(f, fname, pal)
"PPM": ppmread(f, fname, pal)
"RLE": rleread(f, fname, pal)
"XPM": xpmread(f, fname, pal)
}
end
## iformat(f) -- return image format of file f
procedure iformat(f)
local s
seek(f, 1)
s := reads(f, 1024) | fail
seek(f, 1)
s ? {
if ="GIF8" then return "GIF"
if ="\xFF\xD8\xFF" then return "JPEG"
if =("P1" | "P4") then return "PBM"
if =("P2" | "P5") then return "PGM"
if =("P3" | "P6") then return "PPM"
if ="\x52\xCC" then return "RLE"
if find("XPM") then return "XPM"
fail
}
end
## xpmread(f, fname, pal) -- read XPM image from file f, named fname
procedure xpmread(f, fname, pal)
local s, w, h, win
s := XPMImage(f, pal) | fail
w := imswidth(s)
h := imsheight(s)
win := WOpen("canvas=hidden", "bg=" || WAttrib("bg"),
"width=" || w, "height=" || h) | fail
DrawImage(win, 0, 0, s)
return win
end
## gifread(f, fname, pal) -- read GIF image from file f, named fname
#
# WOpen("image=") is avoided because that consumes color palette entries.
# Instead, the image is read using ReadImage with a palette restriction.
#
# gifread returns a window containing the image.
# It fails if for any reason the image cannot be read.
procedure gifread(f, fname, pal)
local w, h, win
$ifdef _JAVA # no palettes, no problem
return WOpen("canvas=hidden", "bg=" || WAttrib("bg"), "image=" || fname)
$else
(reads(f, 10) | fail) ? { # read header
="GIF8" | fail
move(2)
w := ord(move(1)) + 256 * ord(move(1)) # width
h := ord(move(1)) + 256 * ord(move(1)) # height
}
win := WOpen("canvas=hidden", "bg=" || WAttrib("bg"),
"width=" || w, "height=" || h) | fail
if ReadImage(win, fname, 0, 0, pal) then
return win
close(win)
fail
$endif
end
## jpegread(f, fname, pal) -- read JPEG image
procedure jpegread(f, fname, pal)
$ifdef _JAVA
return WOpen("canvas=hidden", "bg=" || WAttrib("bg"), "image=" || fname)
$else
needprog("djpeg") | fail
return filter("djpeg -g 2>/dev/null", fname, pal)
$endif
end
## ppmread(f, fname, pal) -- read PPM file
procedure ppmread(f, fname, pal)
$ifdef _DYNAMIC_LOADING
return rawppm(f, fname, pal) | pnmread(f, fname, pal)
$else
return pnmread(f, fname, pal)
$endif
end
## pnmread(f, fname, pal) -- read PPM/PGM/PBM image by spawning converter
procedure pnmread(f, fname, pal)
needprog("ppmquant") | fail
needprog("ppmtogif") | fail
return filter("ppmquant 256 2>/dev/null | ppmtogif 2>/dev/null", fname, pal)
end
$ifdef _DYNAMIC_LOADING
## rawppm(f, fname, pal) -- read raw-mode PPM file f, named fname
procedure rawppm(f, fname, pal)
local w, h, s, win
s := ""
while s ||:= reads(f, 200000)
w := ppmwidth(s) | fail
h := ppmheight(s)
win := WOpen("canvas=hidden", "width=" || w, "height=" || h) | fail
DrawImage(win, 0, 0, ppmimage(s, pal, ""))
return win
end
$endif # _DYNAMIC_LOADING
## rleread(f, fname, pal) -- read Utah RLE image
procedure rleread(f, fname, pal)
needprog("rlequant") | fail
needprog("rletogif") | fail
return filter("rlequant | rletogif", fname, pal)
end
## filter(cmd, fname, pal) -- run filter to produce GIF file
procedure filter(cmd, fname, pal)
local win, f
remove(tempname)
cmd := "<" || fname || " " || cmd || " >" || tempname
if \opts["d"] then
write(&errout, "+ ", cmd)
system(cmd)
f := open(tempname) | fail
win := gifread(f, tempname, pal)
close(f)
remove(tempname)
return \win
end
## needprog(s) -- check for presence of program s in $PATH
#
# Fails if the program is not available.
# Issues a diagnostic only once per program.
procedure needprog(s)
static ptable
initial ptable := table()
/ptable[s] := pathfind(s, map(getenv("PATH"), ":", " ")) |
(write(&errout, "can't find program \"", s, "\" in $PATH") & "")
return "" ~=== ptable[s]
end