############################################################################ # # File: findtile.icn # # Subject: Program to find tiles in an image # # Author: Ralph E. Griswold # # Date: January 7, 1999 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # This program is designed to assist in locating areas within an image # that, when tiled, produce a desired effect. For example, a background # may consist of a tiled image; this program can be used to find the # smallest tile for the repeat (by "eye-balling"). It's worth noting # that interesting images can be found for other settings. For example, # another interesting use of this program is to produce striped patterns by # selecting a row or column of an image to get a tile that is one character # wide. Sometimes a few rows or columns give an interesting "fabric" # effect. # # There are three windows: # # the VIB control window # the source image window # a repeat window, which shows the selection from the source # image, tiled. # # The selection from the source image is shown as a marquee in the # source image window. When a source image is loaded, the marquee starts # with the entire image. The marquee can be changed by buttons and # arrow-key events on the control window (not the source image window). # # The arrow keys have two modes. With no modifier, they nudge the # location of the marquee. With the meta-key modifier, they nudge # the dimensions of the marquee. # # The reset button resets the marquee to the entire image. # # The current selection can be mirrored using the mirror button. # # The following features are provided through keyboard shortcuts: # the File menu, and in some cases, on-board buttons: # # @O open new source image # @Q quit application # @S save current selection as an image # @Z set size precisely # # The repeat window can be resized by the user, but it is not redrawn # until the marque is changed or the refresh button is pushed. # ############################################################################ # # Requires: Version 9 graphics # ############################################################################ # # Links: grecords, interact, mirror, tile # ############################################################################ # # Includes: keysyms.icn # ############################################################################ link grecords link interact link mirror link tile $include "keysyms.icn" # Globals related to windows: global controls # VIB control window global pattern # repeat window global screen # source image window visible global source # source image window hidden global symmetry # mirroring window global posx # x position relative to interface window global posy # y position relative to repeat window # Globals related to the selection: global current # current selection record global hmax # maximum height of source image global wmax # maximum width of source image global previous # previous selection record global vidgets # table of interface vidgets procedure main() local atts, x1, y1 atts := ui_atts() put(atts, "posx=10", "posy=10") controls := (WOpen ! atts) | ExitNotice("Cannot open control window.") vidgets := ui() init() repeat { while *Pending(controls) > 0 do ProcessEvent(vidgets["root"], , shortcuts) while *Pending(\screen) > 0 do if Event(screen) === &lpress then draw_marquee() } end # Callback that handles all the buttons that change x, y, w, and h. procedure adjust_cb(vidget, value) check_source() | fail # Cute code alert: The selected reversible assignment is performed # and passed to check(). It checks the resulting selection rectangle # and fails if it's not valid. That failure causes the reversible # assignment to be undone and the expression fails, leaving the # selection as it was. case value[1] of { "w max" : current.w := (wmax - current.x) "h max" : current.h := (hmax - current.y) "w = 1" : current.w := 1 "h = 1" : current.h := 1 "full" : { current.h := hmax current.w := wmax current.x := 0 current.y := 0 } "w / 2" : check(current.w <- current.w / 2) "h / 2" : check(current.h <- current.h / 2) "w * 2" : check(current.w <- current.w * 2) "h * 2" : check(current.h <- current.h * 2) } show() return end procedure draw_marquee() local x1, y1 current.x := &x current.y := &y current.h := current.w := 0 update() repeat { case Event(screen) of { &ldrag : update_marquee() &lrelease : { update_marquee() Raise(controls) return } } } end procedure update_marquee() if &x < 0 then &x := 0 if &y < 0 then &y := 0 if &x > wmax then &x := wmax if &y > hmax then &y := hmax current.w := &x - current.x current.h := &y - current.y show() return end procedure location_cb(vidget, value) check_source() | fail # Cute code alert: The selected reversible assignment is performed # and passed to check(). It checks the resulting selection rectangle # and fails if it's not valid. That failure causes the reversible # assignment to be undone and the expression fails, leaving the # selection as it was. case value[1] of { "nw" : current.x := current.y := 0 "ne" : { current.x := wmax - current.w current.y := 0 } "se" : { current.x := wmax - current.w current.y := hmax - current.h } "sw" : { current.x := 0 current.y := hmax - current.h } "x max" : current.x := wmax - current.w "y max" : current.y := hmax - current.h "center" : { current.x := (wmax - current.w) / 2 current.y := (hmax - current.h) / 2 } "home" : { current.x := 0 current.y := 0 } "x / 2" : current.x <- current.x / 2 "y / 2" : current.y <- current.y / 2 "x * 2" : check(current.x <- current.x * 2) "y * 2" : check(current.y <- current.y * 2) } show() return end # Check validity of selection. procedure check() if (0 <= current.w <= (wmax - current.x)) & (0 <= current.h <= (hmax - current.y)) & (0 <= current.x <= hmax) & (0 <= current.y <= wmax) then return else { Alert() fail } end # Copy hidden source window to a visible window. procedure copy_source(label) screen := WOpen( "size=" || WAttrib(source, "width") || "," || WAttrib(source, "height"), "posx=" || posx, "posy=" || posy, "label=" || label, "drawop=reverse", "linestyle=onoff" ) | ExitNotice("Cannot open image window.") CopyArea(source, screen) Raise(controls) wmax := WAttrib(source, "width") hmax := WAttrib(source, "height") WAttrib(pattern, "width=" || (WAttrib(screen, "width"))) WAttrib(pattern, "height=" || (WAttrib(screen, "height"))) EraseArea(pattern) current := rect(0, 0, wmax, hmax) show() return end # Handle file menu. procedure file_cb(vidget, value) case value[1] of { "open @O" : get_image() "quit @Q" : quit_cb() "save @S" : save_cb() } return end # Get new source image. procedure get_image() WClose(\source) WClose(\screen) WClose(\symmetry) EraseArea(pattern) repeat { (OpenDialog("Open image:") == "Okay") | fail source := WOpen("canvas=hidden", "image=" || dialog_value) | { Notice("Can't open " || dialog_value || ".") next } copy_source(dialog_value) wmax := WAttrib(source, "width") hmax := WAttrib(source, "height") break } return end # These values are for Motif; they may need to be changed for other # window managers. $define Offset1 32 $define Offset2 82 # Initialize the program. procedure init() local iheight posx := WAttrib(controls, "width") + Offset1 iheight := WAttrib(controls, "height") pattern := WOpen("label=repeat", "resize=on", "size=" || iheight || "," || iheight, "posx=" || posx, "posy=10") | ExitNotice("Cannot open pattern window.") posy := WAttrib(pattern, "height") + Offset2 Raise(controls) return end procedure update() static sx, sy initial { sx := vidgets["marker"].ax sy := vidgets["marker"].ay } # Update selection information on interface. WAttrib(controls, "drawop=reverse") DrawString(controls, sx, sy, "marquee: x=" || (\previous).x || " y=" || previous.y || " w=" || previous.w || " h=" || previous.h) DrawString(controls, sx, sy, "marquee: x=" || current.x || " y=" || current.y || " w=" || current.w || " h=" || current.h) WAttrib(controls, "drawop=copy") # Update the selection rectangle. DrawRectangle(screen, (\previous).x, previous.y, previous.w, previous.h) DrawRectangle(screen, current.x, current.y, current.w, current.h) previous := copy(current) return end procedure mirror_cb() check_source() | fail # Normalize selection rectangle. if current.w < 0 then { current.w := -current.w current.x -:= current.w } if current.h < 0 then { current.h := -current.h current.y -:= current.h } WClose(\symmetry) symmetry := mirror(source, current.x, current.y, current.w, current.h) | { Notice("Cannot mirror tile.") fail } # In case the window manager opens a window larger than requested ... tile(symmetry, pattern, 0, 0, current.w * 2, current.h * 2) # Hide it but keep it in case the user wants to save it. # WAttrib(symmetry, "canvas=hidden") Raise(controls) return end # Terminate program execution. procedure quit_cb() exit() end procedure refresh_cb() tile(source, pattern, current.x, current.y, current.w, current.h) return end # Callback procedure to allow use of standard tile sizes. procedure size_cb(vidget, value) local dim check_source() | fail if value[1] == "set @Z" then { set_size() return } value[1] ? { dim := tab(upto('x')) } check(current.w <- current.h <- dim) | fail show() return end # Setting of specific selection rectangle values. procedure set_size() repeat { if TextDialog("Set values:", ["x", "y", "w", "h"], [current.x, current.y, current.w, current.h ] ) == "Cancel" then fail check( current.x <- integer(dialog_value[1]) & current.y <- integer(dialog_value[2]) & current.w <- integer(dialog_value[3]) & current.h <- integer(dialog_value[4]) ) | { Notice("Invalid value.") next } show() return } end # Keyboard shortcuts. procedure shortcuts(e) case type(e) of { "string" : { if &meta then case map(e) of { # fold case "m" : mirror_cb() "o" : get_image() "q" : exit() "s" : save_cb() "z" : set_size() } } "integer" : { if &meta then { # nudge dimensions if check( case e of { Key_Left : current.w <- current.w - 1 Key_Right : current.w <- current.w + 1 Key_Up : current.h <- current.h - 1 Key_Down : current.h <- current.h + 1 } ) then show() else fail } else { # nudge location if check ( case e of { Key_Left : current.x <- current.x - 1 Key_Right : current.x <- current.x + 1 Key_Up : current.y <- current.y - 1 Key_Down : current.y <- current.y + 1 } ) then show() else fail } } } return end # Show selection tiled. procedure show() local x, y, w, h check_source() | fail x := current.x y := current.y w := current.w h := current.h if w < 0 then x -:= (w := -w) if h < 0 then y -:= (h := -h) tile(source, pattern, x, y, w, h) update() return end # Save current selection. procedure save_cb() check_source() | fail return snapshot(source, current.x, current.y, current.w, current.h) end # Check for source image. procedure check_source() \source | { Notice("No source image.") fail } return end #===<>=== modify using vib; do not remove this marker line procedure ui_atts() return ["size=360,243", "bg=pale gray", "label=Tile Finder"] end procedure ui(win, cbk) return vsetup(win, cbk, [":Sizer:::0,0,360,243:Tile Finder",], ["adjust:Menu:pull::131,1,50,21:Adjust",adjust_cb, ["home","w max","h max","w * 2","h * 2", "w / 2","h / 2","w = 1","h = 1"]], ["file:Menu:pull::0,1,36,21:File",file_cb, ["open @O","save @S","save mirrored","quit @Q"]], ["line1:Line:::0,22,360,22:",], ["location:Menu:pull::35,0,64,21:Location",location_cb, ["nw","ne","se","sw","center", "x max","y max","x * 2","y * 2","x / 2", "y / 2"]], ["mirror:Button:regular::100,41,58,20:mirror",mirror_cb], ["refresh:Button:regular::22,41,58,20:refresh",refresh_cb], ["size:Menu:pull::98,0,36,21:Size",size_cb, ["set @Z","4x4","8x8","16x16","32x32", "64x64","72x72","96x96","100x100","128x128", "200x200","256x256","400x400","512x512"]], ["marker:Rect:invisible::8,110,32,20:",], ) end #===<>=== end of section maintained by vib