Peripheral drift illusion
- Task
Generate and display a Peripheral Drift Illusion
The image appears to be moving even though it is perfectly static.
Provide a link to show the output, either by running the code online or a screenshot uploaded to a suitable image host.
- References
Julia
Line color tables taken from the Wren example. See the output on imgur. <lang julia>using Gtk, Colors, Cairo
function CodepenApp()
# left-top, top-right, right-bottom, bottom-left LT, TR, RB, BL = 1, 2, 3, 4 edges = [ [LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB], [LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL], [TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL], [TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT], [RB, TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT], [RB, RB, TR, TR, LT, LT, BL, BL, RB, RB, TR, TR], [BL, RB, RB, TR, TR, LT, LT, BL, BL, RB, RB, TR], [BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB, RB], [LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB], [LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL], [TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL], [TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT]] W, B = colorant"white", colorant"darkgray" colors = [ [W, B, B, W], [W, W, B, B], [B, W, W, B], [B, B, W, W]] win = GtkWindow("Peripheral drift illusion", 230, 230) |> (can = GtkCanvas()) @guarded draw(can) do widget ctx = Gtk.getgc(can) function line(x1, y1, x2, y2, colr) set_source(ctx, colr) move_to(ctx, x1, y1) line_to(ctx, x2, y2) stroke(ctx) end set_source(ctx, colorant"yellow") rectangle(ctx, 0, 0, 250, 250) fill(ctx) set_line_width(ctx, 2) for x in 1:12 px = 18 + x * 14 for y in 1:12 py = 18 + y * 14 set_source(ctx, colorant"skyblue") rectangle(ctx, px, py, 10, 10) fill(ctx) carray = colors[edges[y][x]] line(px, py, px + 9, py, carray[1]) line(px + 9, py, px + 9, py + 9, carray[2]) line(px + 9, py + 9, px, py + 9, carray[3]) line(px, py + 9, px, py, carray[4]) end end end showall(win) draw(can) condition = Condition() endit(w) = notify(condition) signal_connect(endit, win, :destroy) showall(win) wait(condition)
end
CodepenApp() </lang>
Nim
A translation using Gtk via the gintro
bindings for Nim, so the code is quite different. We choose also different sizes for the window and the grid, and colors closer to those of the codepen demo.
<lang Nim>import gintro/[glib, gobject, gtk, gio, cairo]
const
Width = 600 Height = 460
type
Color = array[3, float] Edge {.pure.} = enum LT, TR, RB, BL
const
Edges = [[LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB], [LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL], [TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL], [TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT], [RB, TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT], [RB, RB, TR, TR, LT, LT, BL, BL, RB, RB, TR, TR], [BL, RB, RB, TR, TR, LT, LT, BL, BL, RB, RB, TR], [BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB, RB], [LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB], [LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL], [TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL], [TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT]]
Black: Color = [0.0, 0.0, 0.0] Blue: Color = [0.2, 0.3, 1.0] White: Color = [1.0, 1.0, 1.0] Yellow: Color = [0.8, 0.8, 0.0]
Colors: array[Edge, array[4, Color]] = [[White, Black, Black, White], [White, White, Black, Black], [Black, White, White, Black], [Black, Black, White, White]]
- ---------------------------------------------------------------------------------------------------
proc draw(area: DrawingArea; context: Context) =
## Draw the pattern in the area.
func line(x1, y1, x2, y2: float; color: Color) = context.setSource(color) context.moveTo(x1, y1) context.lineTo(x2, y2) context.stroke
context.setSource(Yellow) context.rectangle(0, 0, Width, Height) context.fill()
for x in 0..11: let px = float(86 + x * 36) for y in 0..11: let py = float(16 + y * 36) context.setSource(Blue) context.rectangle(px, py, 24, 24) context.fill() let carray = Colors[Edges[y][x]] context.setLineWidth(2) line(px, py, px + 23, py, carray[0]) line(px + 23, py, px + 23, py + 23, carray[1]) line(px + 23, py + 23, px, py + 23, carray[2]) line(px, py + 23, px, py, carray[3])
- ---------------------------------------------------------------------------------------------------
proc onDraw(area: DrawingArea; context: Context; data: pointer): bool =
## Callback to draw/redraw the drawing area contents.
area.draw(context) result = true
- ---------------------------------------------------------------------------------------------------
proc activate(app: Application) =
## Activate the application.
let window = app.newApplicationWindow() window.setSizeRequest(Width, Height) window.setTitle("Peripheral drift illusion")
# Create the drawing area. let area = newDrawingArea() window.add(area)
# Connect the "draw" event to the callback to draw the pattern. discard area.connect("draw", ondraw, pointer(nil))
window.showAll()
- ———————————————————————————————————————————————————————————————————————————————————————————————————
let app = newApplication(Application, "Rosetta.Illusion") discard app.connect("activate", activate) discard app.run()</lang>
Phix
You can run this online here.
-- -- demo\rosetta\Peripheral_Drift_Illusion.exw -- ========================================== -- -- converted from https://codepen.io/josetxu/pen/rNmXjrq -- with javascript_semantics include pGUI.e Ihandle dlg, canvas cdCanvas cdcanvas constant title = "Peripheral Drift Illusion", CD_LIGHT_OLIVE = #d3d004, CD_PALE_BLUE = #3250ff, dxy = {{45,45},{0,45},{0,0},{45,0},{45,45},{0,45}} function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*posy*/) integer {width, height} = IupGetIntInt(canvas, "DRAWSIZE") cdCanvasActivate(cdcanvas) cdCanvasSetBackground(cdcanvas, CD_LIGHT_OLIVE) cdCanvasClear(cdcanvas) integer c = 0, cy = floor(height/2)*2-81 while cy>100 do integer d = c, cx = 81 while cx<width-100 do cdCanvasSetForeground(cdcanvas, CD_WHITE) cdCanvasBox(cdcanvas, cx, cx+45, cy, cy-45) cdCanvasSetForeground(cdcanvas, CD_BLACK) cdCanvasBegin(cdcanvas, CD_FILL) for i=4 to 6 do integer {dy,dx} = dxy[i-d] cdCanvasVertex(cdcanvas, cx+dx, cy-dy) end for cdCanvasEnd(cdcanvas) cdCanvasSetForeground(cdcanvas, CD_PALE_BLUE) cdCanvasBox(cdcanvas, cx+4, cx+41, cy-4, cy-41) d = remainder(d+(odd(cx)==odd(cy)),4) cx += 63 end while c = remainder(c+4-even(cy),4) cy -= 63 end while cdCanvasFlush(cdcanvas) return IUP_DEFAULT end function function map_cb(Ihandle ih) atom res = IupGetDouble(NULL, "SCREENDPI")/25.4 IupGLMakeCurrent(canvas) if platform()=JS then cdcanvas = cdCreateCanvas(CD_IUP, canvas) else cdcanvas = cdCreateCanvas(CD_GL, "10x10 %g", {res}) end if cdCanvasSetBackground(cdcanvas, CD_PARCHMENT) return IUP_DEFAULT end function function canvas_resize_cb(Ihandle /*canvas*/) integer {cw, ch} = IupGetIntInt(canvas, "DRAWSIZE") atom res = IupGetDouble(NULL, "SCREENDPI")/25.4 cdCanvasSetAttribute(cdcanvas, "SIZE", "%dx%d %g", {cw, ch, res}) return IUP_DEFAULT end function procedure main() IupOpen() canvas = IupGLCanvas("RASTERSIZE=780x600") -- (sensible restore size) sequence cb = {"MAP_CB", Icallback("map_cb"), "ACTION", Icallback("redraw_cb"), "RESIZE_CB", Icallback("canvas_resize_cb")} IupSetCallbacks(canvas, cb) dlg = IupDialog(canvas,`TITLE="%s",PLACEMENT=MAXIMIZED`,{title}) IupShow(dlg) if platform()!=JS then IupMainLoop() IupClose() end if end procedure main()
Raku
<lang perl6>use SVG;
my @blocks = (1..15 X 1..10).map: -> ($X, $Y) {
my $r = (($X + $Y) div 2) % 4 * 90; my $x = $X * 75; my $y = $Y * 75; :use['xlink:href' => '#block', 'transform' => "rotate($r,$x,$y) translate($x,$y)"]
}
'peripheral-drift-raku.svg'.IO.spurt: SVG.serialize(
svg => [ :1200width, :825height, :rect[:width<100%>, :height<100%>, :fill<#d3d004>], :defs[ :g[ :id<block>, :polygon[:points<-25,-25,-25,25,25,25>, :style<fill:white;stroke:white>], :polygon[:points<25,25,25,-25,-25,-25>, :style<fill:black;stroke:black>], :rect[:x<-20>, :y<-20>, :width<40>, :height<40>, :fill<#3250ff>] ] ], |@blocks, ]
)</lang> See offsite SVG image
Wren
This reproduces the codepen image and does indeed appear to move although it's static. See the output on imgur <lang ecmascript>import "dome" for Window import "graphics" for Canvas, Color
// signifies the white edges on the blue squares var LT = 0 var TR = 1 var RB = 2 var BL = 3
var Edges = [
[LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB], [LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL], [TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL], [TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT], [RB, TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT], [RB, RB, TR, TR, LT, LT, BL, BL, RB, RB, TR, TR], [BL, RB, RB, TR, TR, LT, LT, BL, BL, RB, RB, TR], [BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB, RB], [LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL, RB], [LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL, BL], [TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT, BL], [TR, TR, LT, LT, BL, BL, RB, RB, TR, TR, LT, LT]
]
var Light_olive = Color.hex("#d3d004") var Pale_blue = Color.hex("#3250ff")
var W = Color.white var B = Color.black
var Colors = [
[W, B, B, W], [W, W, B, B], [B, W, W, B], [B, B, W, W]
]
class PeripheralDrift {
construct new() { Window.resize(1000, 1000) Canvas.resize(1000, 1000) Window.title = "Peripheral drift illusion" }
init() { Canvas.cls(Light_olive) for (x in 0..11) { var px = 90 + x * 70 for (y in 0..11) { var py = 90 + y * 70 Canvas.rectfill(px, py, 50, 50, Pale_blue) drawEdge(px, py, Edges[y][x]) } } }
drawEdge(px, py, edge) { var c = Colors[edge] Canvas.line(px, py, px + 46, py, c[0], 4) Canvas.line(px + 46, py, px + 46, py + 46, c[1], 4) Canvas.line(px, py + 46, px + 46, py + 46, c[2], 4) Canvas.line(px, py + 46, px, py, c[3], 4) }
update() {}
draw(alpha) {}
}
var Game = PeripheralDrift.new()</lang>