############################################################################
#
#	File:     img.icn
#
#	Subject:  Program to create and edit tiny images
#
#	Authors:  Gregg M. Townsend and Nolan Clayton
#
#	Date:     April 9, 2004
#
############################################################################
#
#  This file is in the public domain.
#
############################################################################
#
#  img is a simple editor of Icon image strings and other tiny images.
#  An image size of 64 x 64 pixels is around the practical maximum.
#
#  usage:  img [-cn | -gn] [filename | width [height]]
#
#  -c or -g specifies a palette; the default is -c1.
#
#  An input file may contain an image string or an image readable by Icon.
#  If no filename is given, a new image (default size 16 x 16) is created.
#
#  img brings up a window within which:
#
#	-- clicking on the color palette sets the color of that mouse button
#	-- clicking on the cell grid sets the color of a cell
#	-- shift-clicking on the cell grid sets the button color from the cell
#
#	-- pressing "W" writes the image string to standard output
#	-- pressing "Q" writes the image string and then exits
#	-- pressing "Z" clears all cells to the color of the left mouse button
#	-- pressing "O" or "L" toggles palette outlining or labeling
#	-- pressing "T" sets the left mouse button to '~' the transparent color
#	-- pressing "R" changes pixels matching the right button color
#		to be the color of the left button
#
############################################################################
#
#  Requires:  Version 9 graphics
#
############################################################################
#
#  Links: graphics, imscolor
#
############################################################################

#  To Do:
#  clearer display of transparent button & pixels
#  add "save as" function to write GIF (or whatever) file
#  use standard utils for row<->image translation


link graphics, imscolor

$define Border 16		# window border

$define ColorW 12		# width of color indicator
$define ColorH 24		# height of color indicator

$define LMar 150		# left margin of cell area
$define MaxCell 24		# maximum cell size


global rows, imspec		# current image
global palette			# color palette
global palx, paly, palw, palh	# palette display area
global palf			# palette display flags
global buttons			# button colors


#  main program

procedure main(args)
   local wwidth, wheight
   local hcells, vcells, cellsize, x0, y0
   local black, white
   local i, j, x, y, k, e, c
   local imgstr, imgtemp, L

   Window(args)
   wwidth := WAttrib("width")			# window width
   wheight := WAttrib("height")			# window height

   palette := "c1"
   args[1] ? if ="-" then {
      palette := tab(0)
      get(args)
      }

   if *args > 0 & not integer(args[1]) then {	# if filename supplied
      imgstr := readicon(args[1])
      palette := imspalette(imgstr)
      hcells := imswidth(imgstr)              	# cells horizontally
      vcells := imsheight(imgstr)		# cells vertically
      }
   else {
      hcells := integer(args[1]) | 16		# cells horizontally
      vcells := integer(args[2]) | hcells	# cells vertically
      c := PaletteKey(palette, "white")
      imgstr := hcells || "," || palette || "," || repl(c, vcells * hcells)
      }

   cellsize := MaxCell				# cell size on window
   cellsize >:= wheight / (vcells + 4)
   cellsize >:= (wwidth - LMar) / (hcells + 4)
   if cellsize < 2 then
      stop("image is too large for this window")

   palx := Border
   paly := Border + vcells + Border
   palw := LMar - 2 * Border
   palh := wheight - Border - paly
   palf := "u"
   drawpalette(palette, palx, paly, palw, palh, palf)

   x0 := wwidth / 2 - (cellsize * hcells) / 2 + LMar / 2  # UL corner of cells
   y0 := wheight / 2 - (cellsize * vcells) / 2
   Fg("gray")
   every x := x0 to x0 + (hcells - 1) * cellsize by cellsize do
      every y := y0 to y0 + (vcells - 1) * cellsize by cellsize do
         DrawRectangle(x, y, cellsize, cellsize)

   black := PaletteKey(palette, "black")
   white := PaletteKey(palette, "white")
   buttons := table()
   setbutton(&lpress, black)
   setbutton(&mpress, black)
   setbutton(&rpress, white)

   imgtemp := imgstr[find(imspalette(imgstr), imgstr) : 0]
   imgtemp := imgtemp[find(',', imgtemp) + 1 : 0]

   rows := []			# list of row values
   L := ""

   every y := 1 to vcells do {
      every x := 1 to hcells do {
         k := imgtemp[((y - 1) * hcells) + x]
         L ||:= k
         Fg(PaletteColor(palette, k))
         FillRectangle(x0 + ((x - 1) * cellsize),
            y0 + ((y - 1) * cellsize), cellsize, cellsize)
         }
      put(rows, L)
      L :=""
      }

   Fg("gray")
   every x := x0 to x0 + (hcells - 1) * cellsize by cellsize do
      every y := y0 to y0 + (vcells - 1) * cellsize by cellsize do
         DrawRectangle(x, y, cellsize, cellsize)

   newimage()

   repeat case e := Event() of {

      &lpress | &mpress | &rpress | &ldrag | &mdrag | &rdrag: {

         # mouse on palette: set color
         if k := pickpalette(palette, &x - palx, &y - paly, palw, palh) then {
            case e of {
               &lpress | &ldrag:  setbutton(&lpress, k)
               &mpress | &mdrag:  setbutton(&mpress, k)
               &rpress | &rdrag:  setbutton(&rpress, k)
               }
            next
            }

         # mouse on cell: set color
         j := (&x - x0) / cellsize
         i := (&y - y0) / cellsize
         if j < 0 | j >= hcells | i < 0 | i >= vcells then
            next
         x := x0 + j * cellsize + 1
         y := y0 + i * cellsize + 1

         # if shifted, pick color from grid 
         if &shift then {
            k := rows[i + 1, j + 1]
            case e of {
               &lpress | &ldrag:  setbutton(&lpress, k)
               &mpress | &mdrag:  setbutton(&mpress, k)
               &rpress | &rdrag:  setbutton(&rpress, k)
               }
            next
            }

         case e of {
            &lpress | &ldrag:  k := buttons[&lpress]
            &mpress | &mdrag:  k := buttons[&mpress]
            &rpress | &rdrag:  k := buttons[&rpress]
            }
         Fg(PaletteColor(palette, k))
         FillRectangle(x, y, cellsize - 1, cellsize - 1)
         rows[i + 1, j + 1] := k
         newimage()
         }

      !"oOlL": {			# O or L: toggle outlining / labeling
         e := map(e)
         if palf ? find(e) then
            palf := string(palf -- e)
         else
            palf ||:= e
         drawpalette(palette, palx, paly, palw, palh, palf)
         }
      QuitEvents(): {			# Q (etc): quit
         imswrite(, imspec)
         exit()
         }
      !"wW": {				# W: write pattern to stdout
         imswrite(, imspec)
         }

      !"tT": {				# T: set left mouse button transparent
         setbutton(&lpress, '~')
         }

      !"rR": {				# R: replace colors
         colorreplace(buttons[&rpress], buttons[&lpress])

         every y := 1 to vcells do {
            every x := 1 to hcells do {
               k := rows[y][x]
               Fg(PaletteColor(palette, k))
               FillRectangle(x0 + ((x - 1) * cellsize),
                  y0 + ((y - 1) * cellsize), cellsize, cellsize)
               }
            }

         Fg("gray")
         every x := x0 to x0 + (hcells - 1) * cellsize by cellsize do
            every y := y0 to y0 + (vcells - 1) * cellsize by cellsize do
               DrawRectangle(x, y, cellsize, cellsize)
         }


      !"zZ": {				# Z: clear pattern

         k := buttons[&lpress]
         Fg(PaletteColor(palette, k))
         rows := list(vcells, repl(k, hcells))

         FillRectangle(x0, y0, hcells * cellsize, vcells * cellsize)
         Fg("gray")
         every x := x0 to x0 + (hcells - 1) * cellsize by cellsize do
            every y := y0 to y0 + (vcells - 1) * cellsize by cellsize do
               DrawRectangle(x, y, cellsize, cellsize)

         newimage()

         }
      }
end


#  setbutton(event, key) -- set the color of a button

procedure setbutton(e, k)
   local i, x, y

   buttons[e] := k
   i := case e of {
      &lpress: 2
      &mpress: 1
      &rpress: 0
      }
   x := palx + palw - ColorW - i * (ColorW * 3 / 2)
   y := (paly - ColorH) / 2
   Fg(PaletteColor(palette, k))
   FillArc(x, y, ColorW, ColorH)
   Fg("black")
   DrawArc(x, y, ColorW, ColorH)
end


#  newimage() -- update image (in memory and onscreen) from rows

procedure newimage()
   imspec := rowstoims(palette, rows)
   DrawImage(Border, Border, imspec)
   return
end


#  rowstoims(pal, rows) -- convert array of rows into image string

procedure rowstoims(pal, rows)
   local w, s, im

   w := *rows[1] | fail
   im := w || "," || pal || ","
   every s := !rows do {
      if *s ~= w then fail
      im ||:= s
      }
   return im
end


# replacecolor(color1, color2) -- replace color1 with color2

procedure colorreplace(color1, color2)
   local i, j

   every i := 1 to *rows do
      while j := find(color1, rows[i]) do
         rows[i][j] := color2

   newimage()

end


#  readicon(fname) -- read image, returning image string

procedure readicon(fname)
   local res, f, x

   f := open(fname) | stop("cannot open " || fname)

   res := ""

   while x := read(f) do {
      x ? {
         if ="#" then
            next

         ="\""
         res ||:= tab(0)
         }

      if res[-1] == "_" then
            res[-1] := ""
      else
         break
      }
   close(f)

   #
   # Check for reasonably valid image
   #
   if imsheight(res) then
      return res
   else {
      if f := open(fname, "g", "image=" || fname, "canvas=hidden") then {
         res := Capture(f, palette)
         close(f)
         if imsheight(res) then return res
         }
      stop("invalid image: " || fname)
      }

end
