############################################################################ # # File: imsutils.icn # # Subject: Procedures to manipulate image specifications # # Author: Ralph E. Griswold # # Date: May 2, 2001 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # This file contains procedures that manipulate string representations for # images. # # patident(imx1, imx2) # XDrawTile(win, xoff, yoff, pattern, magnif, mode) # XDrawRows(win, xoff, yoff, imx, magnif, mode) # bits2hex(s) # decspec(pattern) # getpatt(line) # getpattnote(line) # hex2bits(s) # hexspec(pattern) # legalpat(tile) # legaltile(tile) # pat2xbm(pattern, name) # tilebits(imx) # pdensity(pattern) # pix2pat(window, x, y, cols, rows) # readims(input) # readimsline(input) # rowbits(pattern) # imstoimx(ims) # imxtoims(imx) # showbits(pattern) # tiledim(pattern) # pheight(pattern) # pwidth(pattern) # xbm2rows(input) # ############################################################################ # # Requires: Version 8.11 graphics # ############################################################################ # # Links: convert # ############################################################################ $include "xnames.icn" link convert record tdim(w, h) # # Test whether two image matrices are equivalent procedure patident(imx1, imx2) local i if *imx1 ~= *imx2 then fail if **imx1 ~= **imx2 then fail every i := 1 to *imx1 do if imx1[i] ~== imx2[1] then fail return imx2 end # # Draw a tile at a given location. If mode is nonnull, the # area on which the tile is drawn is erased. procedure XDrawTile(win, xoff, yoff, pattern, magnif, mode) local x, y, row, pixel, dims, arglist if type(win) ~== "window" then { win :=: xoff :=: yoff :=: pattern :=: mode win := &window } if magnif = 1 then XDrawImage(win, xoff, yoff, pattern, mode) else { if \mode then { dims := tiledim(pattern) XEraseArea(xoff, yoff, dims.w * magnif, dims.h * magnif) } y := yoff every row := rowbits(pattern) do { # draw a row x := xoff arglist := [] every pixel := !row do { if pixel = "1" then put(arglist, x, y, magnif, magnif) x +:= magnif } y +:= magnif if *arglist = 0 then next XFillRectangle ! arglist } } return end # # Draw image matrix at a given location. If mode is nonnull, the # area on which the tile is drawn is erased. procedure XDrawRows(win, xoff, yoff, imx, magnif, mode) local x, y, row, pixel, arglist if type(win) ~== "window" then { win :=: xoff :=: yoff :=: imx :=: magnif :=: mode win := &window } /magnif := 1 y := yoff if \mode then XEraseArea(xoff, yoff, *imx[1] * magnif, *imx * magnif) every row := !imx do { # draw a row x := xoff arglist := [] if magnif = 1 then { every pixel := !row do { if pixel == "1" then put(arglist, x, y) x +:= 1 } y +:= 1 } else { every pixel := !row do { if pixel = "1" then put(arglist, x, y, magnif, magnif) x +:= magnif } y +:= magnif } if *arglist = 0 then next if magnif = 1 then XDrawPoint ! arglist else XFillRectangle ! arglist } return end # # Convert bit string to hex pattern string procedure bits2hex(s) static bittab local hex initial { bittab := table() bittab["0000"] := "0" bittab["1000"] := "1" bittab["0100"] := "2" bittab["1100"] := "3" bittab["0010"] := "4" bittab["1010"] := "5" bittab["0110"] := "6" bittab["1110"] := "7" bittab["0001"] := "8" bittab["1001"] := "9" bittab["0101"] := "a" bittab["1101"] := "b" bittab["0011"] := "c" bittab["1011"] := "d" bittab["0111"] := "e" bittab["1111"] := "f" } hex := "" s ? { while hex := bittab[move(4)] || hex if not pos(0) then hex := bittab[left(tab(0), 4, "0")] || hex } return hex end # # Convert pattern specification to decimal form procedure decspec(pattern) local cols, chunk, dec pattern ? { if not upto("#") then return pattern cols := tab(upto(',')) move(2) chunk := (cols + 3) / 4 dec := cols || "," while dec ||:= integer("16r" || move(chunk)) || "," } return dec[1:-1] end # # Get pattern from line. It trims off leading and trailing whitespace # and removes any annotation (beginning with a # after the first whitespace procedure getpatt(line) line ? { tab(many(' \t')) return tab(upto(' \t') | 0) } end # # Get pattern annotation. It returns an empty string if there is # no annotation. procedure getpattnote(line) line ? { tab(many(' \t')) # remove leading whitespace tab(upto(' \t')) | return "" # skip pattern tab(upto('#')) | return "" # get to annotation tab(many('# \t')) # get rid of leading junk return tab(0) # annotation } end # Convert hexadecimal string to bits procedure hex2bits(s) static hextab local bits initial { hextab := table() hextab["0"] := "0000" hextab["1"] := "0001" hextab["2"] := "0010" hextab["3"] := "0011" hextab["4"] := "0100" hextab["5"] := "0101" hextab["6"] := "0110" hextab["7"] := "0111" hextab["8"] := "1000" hextab["9"] := "1001" hextab["a"] := "1010" hextab["b"] := "1011" hextab["c"] := "1100" hextab["d"] := "1101" hextab["e"] := "1110" hextab["f"] := "1111" } bits := "" map(s) ? { while bits ||:= hextab[move(1)] } return bits end # # Convert pattern to hexadecimal form procedure hexspec(pattern) local cols, chunk, hex pattern ? { if find("#") then return pattern cols := tab(upto(',')) move(1) chunk := (cols + 3) / 4 hex := cols || ",#" while hex ||:= right(exbase10(tab(upto(',') | 0), 16), chunk, "0") do move(1) | break } return hex end # # Succeed if tile is legal and small enough for (X) pattern. Other # windows systems may be more restrictive. procedure legalpat(tile) if not legaltile(tile) then fail tile ? { if 0 < integer(tab(upto(','))) <= 32 then return tile else fail } end # # Succeed if tile is legal. Accepts tiles that are too big for # patterns. procedure legaltile(tile) map(tile) ? { # first check syntax (tab(many(&digits)) & =",") | fail if ="#" then (tab(many('0123456789abcdef')) & pos(0)) | fail else { while tab(many(&digits)) do { if pos(0) then break # okay; end of string else ="," | fail } if not pos(0) then fail # non-digit } } return hexspec(decspec(tile)) == tile end # # Convert pattern specification to an XBM image file. procedure pat2xbm(pattern, name) local dims, chunk, row /name := "noname" dims := tiledim(pattern) write("#define ", name, "_width ", dims.w) write("#define ", name, "_height ", dims.h) write("static char ", name, "_bits[] = {") chunk := (dims.w + 3) / 4 pattern ? { tab(upto('#') + 1) while row := move(chunk) do { if *row % 2 ~= 0 then row := "0" || row row ? { tab(0) while writes("0x", move(-2), ",") } write() } } write("};") end # # Count the number of bits set in a tile procedure tilebits(imx) local bits bits := 0 every bits +:= !!imx return bits end # # Compute density (percentage of black bits) of pattern procedure pdensity(pattern) local dark, dims dims := tiledim(pattern) hexspec(pattern) ? { dark := 0 every rowbits(pattern) ? { every upto('1') do dark +:= 1 } return dark / real(dims.w * dims.h) } end # # Procedure to produce pattern specification from a section of a window. procedure pix2pat(window, x, y, cols, rows) local c, tile, pattern, pixels, y0 pattern := "" every y0 := 0 to rows - 1 do { pixels := "" every c := Pixel(window, x, y0 + y, cols, 1) do pixels ||:= (if c == "0,0,0" then "1" else "0") pattern ||:= bits2hex(pixels) } if *pattern = 0 then fail # out of bounds specification else return cols || ",#" || pattern end # # Read pattern. It skips lines starting with a #, # empty lines, and trims off any trailing characters after the # first whitespace of a pattern. procedure readims(input) local line while line := read(input) do line ? { if pos(0) | ="#" then next return tab(upto(' \t') | 0) } fail end # # Read pattern line. It skips lines starting with a # and empty lines but # does not trim off any trailing characters after the first whitespace of # a pattern. procedure readimsline(input) local line while line := read(input) do line ? { if pos(0) | ="#" then next return tab(0) } fail end # # Generate rows of bits in a pattern. Doesn't work correctly for small # patterns. (Why?) procedure rowbits(pattern) local row, dims, chunk, hex dims := tiledim(pattern) hexspec(pattern) ? { tab(upto(',') + 2) hex := tab(0) chunk := *hex / dims.h hex ? { while row := right(hex2bits(move(chunk)), dims.w, "0") do suspend reverse(row) } } end # # Produce an image matrix from a image string procedure imstoimx(ims) local imx imx := [] every put(imx, rowbits(ims)) return imx end # # Convert row list to pattern specification procedure imxtoims(imx) local pattern pattern := *imx[1] || ",#" every pattern ||:= bits2hex(!imx) return pattern end # Show bits of a pattern procedure showbits(pattern) every write(rowbits(pattern)) write() return end # # Produce dimensions of the tile for a pattern procedure tiledim(pattern) local cols hexspec(pattern) ? { cols := integer(tab(upto(','))) move(2) return tdim(cols, *tab(0) / ((cols + 3) / 4)) } end # # Produce height of a pattern specification procedure pheight(pattern) local cols hexspec(pattern) ? { cols := integer(tab(upto(','))) move(2) return *tab(0) / ((cols + 3) / 4) } end # # Produce width of a pattern specification procedure pwidth(pattern) hexspec(pattern) ? { return integer(tab(upto(','))) } end # # Generate rows of bits from an XBM file. Note: This apparently # is not quite right if there are more than 2 hex digits per # literal. procedure xbm2rows(input) local imagex, bits, row, hex, width, height, chunks static hexdigit initial hexdigit := &digits ++ 'abcdef' imagex := "" read(input) ? { tab(find("width") + 6) tab(upto(&digits)) width := integer(tab(many(&digits))) } read(input) ? { tab(find("height") + 6) tab(upto(&digits)) height := integer(tab(many(&digits))) } chunks := (width / 8) + if (width % 8) > 0 then 1 else 0 while imagex ||:= reads(input, 500000) # Boo! -- can do better imagex ? { every 1 to height do { row := "" every 1 to chunks do { (hex := tab(any(hexdigit)) || tab(any(hexdigit))) | { tab(find("0x") + 2) hex := move(2) } row ||:= case hex of { "00": "00000000" "ff": "11111111" default: reverse(right(hex2bits(hex), 8, "0")) } } suspend left(row, width) } } end