implement Mand; # # Copyright © 2000 Vita Nuova Limited. All rights reserved. # # mandelbrot/julia fractal browser: # button 1 - drag a rectangle to zoom into # button 2 - (from mandel only) show julia at point # button 3 - zoom out include "sys.m"; sys : Sys; include "draw.m"; draw : Draw; Point, Rect, Image, Context, Screen, Display : import draw; include "tk.m"; tk: Tk; include "wmlib.m"; wmlib: Wmlib; include "cvsimages.m"; cvsimages: Cvsimages; Cvsimage: import cvsimages; Mand : module { init : fn(nil : ref Context, argv : list of string); }; colours: array of ref Image; stderr : ref Sys->FD; FIX: type big; Calc: adt { xr, yr: array of FIX; parx, pary: FIX; # column order dispbase: array of COL; # auxiliary display and border imgch: chan of (ref Image, Rect); img: ref Image; maxx, maxy, supx, supy: int; disp: int; # origin of auxiliary display morj : int; winr: Rect; kdivisor: int; pointsdone: int; }; # BASE, LIMIT, MAXCOUNT, MINDELTA may be varied # # calls with 256X128 on initial set # --------------------------------- # crawl 58 (5% of time) # fillline 894 (6% of time) # isblank 5012 (0% of time) # mcount 6928 (55% of time) # getcolour 52942 (11% of time) # displayset 1 (15% of time) # WHITE : con 16r0; BLACK : con 16rff; COL : type byte; BASE : con 60; # 28 HBASE : con (BASE/2); SCALE : con (big 1< r: Rect; Julia => p: Point; Zoomout or Restart => # nothing } }; badmod(mod: string) { sys->fprint(stderr, "mand: cannot load %s: %r\n", mod); sys->raise("fail:bad module"); } win_config := array[] of { "frame .f", "label .f.dl -text Depth", "entry .f.depth", ".f.depth insert 0 1", "checkbutton .f.fill -text {Fill} -command {send cmd fillchanged} -variable fill", ".f.fill select", "pack .f.dl -side left", "pack .f.fill -side right", "pack .f.depth -side top -fill x", "canvas .c -bd 3 -relief sunken -width " + string WIDTH + " -height " + string HEIGHT, "pack .f -side top -fill x", "pack .c -side bottom -fill both -expand 1", "pack propagate . 0", "bind . {send cmd resize}", "bind .c {send cmd b1 %x %y}", "bind .c {send cmd b2 %x %y}", "bind .c {send cmd b1r %x %y}", "bind .c {send cmd b3 %x %y}", "bind .f.depth {send cmd setkdivisor}", "update", }; init(ctxt: ref Context, argv : list of string) { sys = load Sys Sys->PATH; stderr = sys->fildes(2); draw = load Draw Draw->PATH; tk = load Tk Tk->PATH; wmlib = load Wmlib Wmlib->PATH; if (wmlib == nil) badmod(Wmlib->PATH); cvsimages = load Cvsimages Cvsimages->PATH; if (cvsimages == nil) badmod(Cvsimages->PATH); cvsimages->init(); if (ctxt == nil) { sys->fprint(stderr, "mand: no draw context\n"); sys->raise("fail:no draw context"); } wmlib->init(); (win, wmcmd) := wmlib->titlebar(ctxt.screen, "", "Fractals", Wmlib->Appl); sys->pctl(Sys->NEWPGRP, nil); cmdch := chan of string; tk->namechan(win, cmdch, "cmd"); for (i := 0; i < len win_config; i++) cmd(win, win_config[i]); R = G = B = 6; argv = tl argv; if (argv != nil) { (R, argv) = (int hd argv, tl argv); if (R <= 0) R = 1; } if (argv != nil) { (G, argv) = (int hd argv, tl argv); if (G <= 0) G = 1; } if (argv != nil) { (B, argv) = (int hd argv, tl argv); if (B <= 0) B = 1; } colours = array[256] of ref Image; for (i = 0; i < len colours; i++) # colours[i] = ctxt.display.color(i); colours[i] = ctxt.display.rgb(col(i/(G*B), R), col(i/(1*B), G), col(i/(1*1), B)); specr := Fracrect((-2.0, -1.5), (1.0, 1.5)); p := Params( correctratio(specr, win), (0.0, 0.0), 1, # m 1, # kdivisor int cmd(win, "variable fill") ); pid := -1; sync := chan of int; imgch := chan of (ref Image, Rect); ci := Cvsimage.new(win, ".c"); win.image.flush(Draw->Flushoff); spawn docalculate(sync, p, imgch); pid = <-sync; imgch <-= (ci.image, ci.r); stack: list of (Fracrect, Params); for(;;) alt { c := <-wmcmd => case c { "move" => if (pid != -1) movewin(ci, imgch, sync); else movewin(ci, nil, nil); * => wmlib->titlectl(win, c); } press := <-cmdch => (n, toks) := sys->tokenize(press, " "); ucmd: ref Usercmd = nil; case hd toks { "start" => ucmd = ref Usercmd.Restart; "resize" => if (ci.config()) ucmd = ref Usercmd.Restart; "b1" or "b2" or "b3" => #cmd(win, "grab set .c"); ci.fix(); ucmd = trackmouse(win, cmdch, hd toks, Point(int hd tl toks, int hd tl tl toks)); #cmd(win, "grab release .c"); "fillchanged" => p.fill = int cmd(win, "variable fill"); ucmd = ref Usercmd.Restart; "setkdivisor" => p.kdivisor = int cmd(win, ".f.depth get"); if (p.kdivisor < 1) p.kdivisor = 1; ucmd = ref Usercmd.Restart; } if (ucmd != nil) { restart := 0; pick u := ucmd { Zoomin => sys->print("zoomin to %s\n", r2s(u.r)); if (u.r.dx() > 0 && u.r.dy() > 0) { stack = (specr, p) :: stack; specr.min = pt2real(u.r.min, win, p.r); specr.max = pt2real(u.r.max, win, p.r); (specr.min.y, specr.max.y) = (specr.max.y, specr.min.y); # canonicalise restart = 1; } Zoomout => if (stack != nil) { ((specr, p), stack) = (hd stack, tl stack); cmd(win, ".f.depth delete 0 end"); cmd(win, ".f.depth insert 0 " + string p.kdivisor); if (p.fill) cmd(win, ".f.fill select"); else cmd(win, ".f.fill deselect"); cmd(win, "update"); restart = 1; } Julia => pt := pt2real(u.p, win, p.r); if (p.m) { stack = (specr, p) :: stack; p.p = pt2real(u.p, win, p.r); specr = ((-2.0, -1.5), (1.0, 1.5)); p.m = 0; restart = 1; } Restart => restart = 1; } if (restart) { if (pid != -1) kill(pid); win.image.flush(Draw->Flushoff); p.r = correctratio(specr, win); sync = chan of int; spawn docalculate(sync, p, imgch); pid = <-sync; imgch <-= (ci.image, ci.r); } } <-sync => win.image.flush(Draw->Flushon); pid = -1; } } correctratio(r: Fracrect, win: ref Tk->Toplevel): Fracrect { # make sure calculation rectangle is in # the same ratio as bitmap (also make sure that # calculated area always includes desired area) wr := canvposn(win); (btall, atall) := (real wr.dy() / real wr.dx(), (r.max.y - r.min.y) / (r.max.x - r.min.x)); if (btall > atall) { # bitmap is taller than area, so expand area vertically excess := (r.max.x - r.min.x) * btall - (r.max.y - r.min.y); r.min.y -= excess / 2.0; r.max.y += excess / 2.0; } else { # area is taller than bitmap, so expand area horizontally excess := (r.max.y - r.min.y) / btall - (r.max.x - r.min.x); r.min.x -= excess / 2.0; r.max.x += excess / 2.0; } return r; } pt2real(pt: Point, win: ref Tk->Toplevel, r: Fracrect): Fracpoint { wr := canvposn(win); return (real (pt.x - wr.min.x) / real wr.dx() * (r.max.x- r.min.x) + r.min.x, real (wr.max.y - pt.y) / real wr.dy() * (r.max.y - r.min.y) + r.min.y); } pt2s(pt: Point): string { return string pt.x + " " + string pt.y; } r2s(r: Rect): string { return pt2s(r.min) + " " + pt2s(r.max); } trackmouse(win: ref Tk->Toplevel, cmdch: chan of string, but: string, p: Point): ref Usercmd { case but { "b1" => r := Rect(p, p); cmd(win, ".c create rectangle " + r2s(r) + " -outline white -width 1 -tags r"); cmd(win, "update"); win.image.flush(Draw->Flushnow); do { but = <-cmdch; (nil, toks) := sys->tokenize(but, " "); (but, r.max.x, r.max.y) = (hd toks, int hd tl toks, int hd tl tl toks); cmd(win, ".c coords r " + r2s(r.canon())); cmd(win, "update"); } while (but != "b1r"); r = r.canon().addpt(canvposn(win).min); cmd(win, ".c delete r; update"); return ref Usercmd.Zoomin(r); "b2" => return ref Usercmd.Julia(p.add(canvposn(win).min)); "b3" => return ref Usercmd.Zoomout; } return nil; } movewin(ci: ref Cvsimage, imgch: chan of (ref Image, Rect), terminated: chan of int) { if (imgch != nil) { # halt calculation process alt { imgch <-= (nil, ((0,0), (0,0))) =>; <-terminated => imgch = nil; } } ci.fix(); wmlib->titlectl(ci.win, "move"); ci.config(); if (imgch != nil) imgch <-= (ci.image, ci.r); # start it again } poll(calc: ref Calc) { calc.img.flush(Draw->Flushnow); alt { <-calc.imgch => calc.img = nil; (calc.img, calc.winr) = <-calc.imgch; calc.img.flush(Draw->Flushoff); * =>; } } docalculate(sync: chan of int, p: Params, imgch: chan of (ref Image, Rect)) { r := p.r; if (p.m) sys->print("mandel [[%g,%g],[%g,%g]]\n", r.min.x, r.min.y, r.max.x, r.max.y); else sys->print("julia [[%g,%g],[%g,%g]] [%g,%g]\n", r.min.x, r.min.y, r.max.x, r.max.y, p.p.x, p.p.y); sync <-= sys->pctl(0, nil); calculate(p, imgch); sync <-= 0; } canvposn(win: ref Tk->Toplevel): Rect { r: Rect; r.min.x = int cmd(win, ".c cget -actx") + int cmd(win, ".c cget -bd"); r.min.y = int cmd(win, ".c cget -acty") + int cmd(win, ".c cget -bd"); r.max.x = r.min.x + int cmd(win, ".c cget -actwidth"); r.max.y = r.min.y + int cmd(win, ".c cget -actheight"); return r; } calculate(p: Params, imgch: chan of (ref Image, Rect)) { calc := ref Calc; (calc.img, calc.winr) = <-imgch; r := calc.winr; calc.maxx = r.dx(); calc.maxy = r.dy(); calc.supx = calc.maxx + 2; calc.supy = calc.maxy + 2; calc.imgch = imgch; calc.xr = array[calc.maxx] of FIX; calc.yr = array[calc.maxy] of FIX; calc.morj = p.m; initr(calc, p); calc.img.draw(r, calc.img.display.zeros, calc.img.display.ones, (0,0)); if (p.fill) { calc.dispbase = array[calc.supx*calc.supy] of COL; # auxiliary display and border calc.disp = calc.maxy + 3; setdisp(calc); displayset(calc); } else { for (x := 0; x < calc.maxx; x++) { for (y := 0; y < calc.maxy; y++) point(calc, calc.img, (x, y), pointcolour(calc, x, y)); } } } setdisp(calc: ref Calc) { d : int; i : int; for (i = 0; i < calc.supx*calc.supy; i++) calc.dispbase[i] = byte BLANK; i = 0; for (d = 0; i < calc.supx; d += calc.supy) { calc.dispbase[d] = byte BORDER; i++; } i = 0; for (d = 0; i < calc.supy; d++) { calc.dispbase[d] = byte BORDER; i++; } i = 0; for (d = 0+calc.supx*calc.supy-1; i < calc.supx; d -= calc.supy) { calc.dispbase[d] = byte BORDER; i++; } i = 0; for (d = 0+calc.supx*calc.supy-1; i < calc.supy; d--) { calc.dispbase[d] = byte BORDER; i++; } } initr(calc: ref Calc, p: Params): int { r := p.r; dp := real2fix((r.max.x-r.min.x)/(real calc.maxx)); dq := real2fix((r.max.y-r.min.y)/(real calc.maxy)); calc.xr[0] = real2fix(r.min.x)-(big calc.maxx*dp-(real2fix(r.max.x)-real2fix(r.min.x)))/big 2; for (x := 1; x < calc.maxx; x++) calc.xr[x] = calc.xr[x-1] + dp; calc.yr[0] = real2fix(r.max.y)+(big calc.maxy*dq-(real2fix(r.max.y)-real2fix(r.min.y)))/big 2; for (y := 1; y < calc.maxy; y++) calc.yr[y] = calc.yr[y-1] - dq; calc.parx = real2fix(p.p.x); calc.pary = real2fix(p.p.y); calc.kdivisor = p.kdivisor; calc.pointsdone = 0; return dp >= MINDELTA && dq >= MINDELTA; } fillline(calc: ref Calc, x, y, d, dir, dird, col: int) { x0 := x; while (calc.dispbase[d] == byte BLANK) { calc.dispbase[d] = byte col; x -= dir; d -= dird; } if (0 && pointcolour(calc, (x0+x+dir)/2, y) != col) { # midpoint of line (island code) # island - undo colouring or do properly do { d += dird; x += dir; # *d = BLANK; calc.dispbase[d] = byte pointcolour(calc, x, y); point(calc, calc.img, (x, y), int calc.dispbase[d]); } while (x != x0); return; # abort crawl ? } horizline(calc, calc.img, x0, x, y, col); } crawlt(calc: ref Calc, x, y, d, col: int) { yinc, dyinc : int; firstd := d; xinc := 1; dxinc := calc.supy; for (;;) { if (getcolour(calc, x+xinc, y, d+dxinc) == col) { x += xinc; d += dxinc; yinc = -xinc; dyinc = -dxinc; # if (isblank(x+xinc, y, d+dxinc)) if (calc.dispbase[d+dxinc] == byte BLANK) fillline(calc, x+xinc, y, d+dxinc, yinc, dyinc, col); if (d == firstd) break; } else { yinc = xinc; dyinc = dxinc; } if (getcolour(calc, x, y+yinc, d+yinc) == col) { y += yinc; d += yinc; xinc = yinc; dxinc = dyinc; # if (isblank(x-xinc, y, d-dxinc)) if (calc.dispbase[d-dxinc] == byte BLANK) fillline(calc, x-xinc, y, d-dxinc, yinc, dyinc, col); if (d == firstd) break; } else { xinc = -yinc; dxinc = -dyinc; } } } # spurious lines problem - disallow all acw paths # # 43---------> # 12---------> # # 654------------> # 7 3------------> # 812------------> # # Given a closed curve completely described by unit movements LRUD (left, # right, up, and down), calculate the enclosed area. The description # may be cw or acw and of arbitrary shape. # # Based on Green's Theorem :- area = integral ydx # C # area = 0; # count = ARBITRARY_VALUE; # while( moves_are_left() ){ # move = next_move(); # switch(move){ # case L: # area -= count; # break; # case R: # area += count; # break; # case U: # count++; # break; # case D: # count--; # break; # } # area = abs(area); crawlf(calc: ref Calc, x, y, d, col: int) { xinc, yinc, dxinc, dyinc : int; firstx, firsty : int; firstd : int; area := 0; count := 0; firstx = x; firsty = y; firstd = d; xinc = 1; dxinc = calc.supy; # acw on success, cw on failure for (;;) { if (getcolour(calc, x+xinc, y, d+dxinc) == col) { x += xinc; d += dxinc; yinc = -xinc; dyinc = -dxinc; area += xinc*count; if (d == firstd) break; } else { yinc = xinc; dyinc = dxinc; } if (getcolour(calc, x, y+yinc, d+yinc) == col) { y += yinc; d += yinc; xinc = yinc; dxinc = dyinc; count -= yinc; if (d == firstd) break; } else { xinc = -yinc; dxinc = -dyinc; } } if (area > 0) # cw crawlt(calc, firstx, firsty, firstd, col); } displayset(calc: ref Calc) { edge : int; last := BLANK; d := calc.disp; for (x := 0; x < calc.maxx; x++) { for (y := 0; y < calc.maxy; y++) { col := calc.dispbase[d]; if (col == byte BLANK) { col = calc.dispbase[d] = byte pointcolour(calc, x, y); point(calc, calc.img, (x, y), int col); if (col == byte last) edge++; else { last = int col; edge = 0; } if (edge >= LIMIT) { crawlf(calc, x, y-edge, d-edge, last); # prevent further crawlf() last = BLANK; } } else { if (col == byte last) edge++; else { last = int col; edge = 0; } } d++; } last = BLANK; d += 2; } } pointcolour(calc: ref Calc, x, y: int) : int { if (++calc.pointsdone >= SCHEDCOUNT) { calc.pointsdone = 0; sys->sleep(0); poll(calc); } if (calc.morj) return mcount(calc, x, y) + 1; else return jcount(calc, x, y) + 1; } mcount(calc: ref Calc, x_coord, y_coord: int): int { (p, q) := (calc.xr[x_coord], calc.yr[y_coord]); (x, y) := (calc.parx, calc.pary); k := 0; maxcount := MAXCOUNT * calc.kdivisor; while (k < maxcount) { if (x >= TWO || y >= TWO || x <= -TWO || y <= -TWO) break; if (0) { # x = (x < 0) ? (x>>HBASE)|NEG : x>>HBASE; # y = (y < 0) ? (y>>HBASE)|NEG : y>>HBASE; } x >>= HBASE; y >>= HBASE; t := y*y; y = big 2*x*y+q; # possible unserious overflow when BASE == 28 x *= x; if (x+t >= FOUR) break; x -= t-p; k++; } return k / calc.kdivisor; } jcount(calc: ref Calc, x_coord, y_coord: int): int { (x, y) := (calc.xr[x_coord], calc.yr[y_coord]); (p, q) := (calc.parx, calc.pary); k := 0; maxcount := MAXCOUNT * calc.kdivisor; while (k < maxcount) { if (x >= TWO || y >= TWO || x <= -TWO || y <= -TWO) break; if (0) { # x = (x < 0) ? (x>>HBASE)|NEG : x>>HBASE; # y = (y < 0) ? (y>>HBASE)|NEG : y>>HBASE; } x >>= HBASE; y >>= HBASE; t := y*y; y = big 2*x*y+q; # possible unserious overflow when BASE == 28 x *= x; if (x+t >= FOUR) break; x -= t-p; k++; } return k / calc.kdivisor; } getcolour(calc: ref Calc, x, y, d: int): int { if (calc.dispbase[d] == byte BLANK) { calc.dispbase[d] = byte pointcolour(calc, x, y); point(calc, calc.img, (x, y), int calc.dispbase[d]); } return int calc.dispbase[d]; } point(calc: ref Calc, d: ref Image, p: Point, col: int) { d.draw(Rect(p, p.add((1,1))).addpt(calc.winr.min), colours[col], d.display.ones, (0,0)); } horizline(calc: ref Calc, d: ref Image, x0, x1, y: int, col: int) { if (x0 < x1) r := Rect((x0, y), (x1, y+1)); else r = Rect((x1+1, y), (x0+1, y+1)); d.draw(r.addpt(calc.winr.min), colours[col], d.display.ones, (0, 0)); # r := Rect((x0, y), (x1, y)).canon(); # r.max = r.max.add((1, 1)); } real2fix(x: real): FIX { return big (x * real SCALE); } cmd(top: ref Tk->Toplevel, s: string): string { e := tk->cmd(top, s); if (e != nil && e[0] == '!') sys->fprint(stderr, "mand: tk error on '%s': %s\n", s, e); return e; } kill(pid: int): int { fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); if (fd == nil) return -1; if (sys->write(fd, array of byte "kill", 4) != 4) return -1; return 0; } col(i, r : int) : int { if (r == 1) return 0; return (255*(i%r))/(r-1); }