#lang scheme/gui
(require (only-in srfi/26 cut)
"util.ss")
(define patch%
(class object%
(init-field size)
(init-field (shape null))
(init-field (foreground-color "black"))
(init-field (background-color "white"))
(define (point-relative->absolute point)
(map (cut * size <>) point))
(define (shape->absolute-points shape)
(map point-relative->absolute shape))
(define (build-path shape)
(let ((path (make-object dc-path%))
(points (shape->absolute-points shape)))
(unless (null? points)
(send/apply path move-to (first points))
(for ((point (rest points)))
(send/apply path line-to point)))
path))
(field (path (build-path shape)))
(define-syntax with-translation
(syntax-rules ()
((_ ?x ?y ?body ...)
(begin
(send path translate ?x ?y)
(begin ?body ...)
(send path translate (- ?x) (- ?y))))))
(define (clear-absolute dc x y)
(send dc set-brush background-color 'solid)
(send dc draw-rectangle x y size size))
(define (draw-absolute dc x y)
(send dc set-brush foreground-color 'solid)
(with-translation x y
(send dc draw-path path)))
(define/public (draw dc x y)
(match-let (((list x y) (point-relative->absolute (list x y))))
(clear-absolute dc x y)
(draw-absolute dc x y)))
(define/public (draw-rotating dc positions)
(for ((x-and-y positions))
(draw dc (first x-and-y) (second x-and-y))
(rotate -90)))
(define/public (rotate degrees)
(with-translation (- (/ size 2)) (- (/ size 2))
(send path rotate (degrees->radians degrees))))
(super-new)))
(define patch-shapes
'(((0 0) (1 0) (1 1) (0 1))
((0 0) (1 0) (0 1))
((0 1) (1/2 0) (1 1))
((0 0) (1/2 0) (1/2 1) (0 1))
((1/2 0) (1 1/2) (1/2 1) (0 1/2))
((0 0) (1 1/2) (1 1) (1/2 1))
((1/2 0) (1 1) (0 1) (1/4 1/2) (1/2 1) (3/4 1/2) (1/4 1/2))
((0 0) (1 1/2) (1/2 1))
((1/4 1/4) (3/4 1/4) (3/4 3/4) (1/4 3/4))
((1/2 0) (1 0) (0 1) (0 1/2) (1/2 1/2) (1/2 0))
((0 0) (1/2 0) (1/2 1/2) (0 1/2))
((0 1/2) (1 1/2) (1/2 1))
((0 1) (1/2 1/2) (1 1))
((1/2 0) (1/2 1/2) (0 1/2))
((0 0) (1/2 0) (0 1/2))
()))
(define center-patch-shapes
(list-refs patch-shapes 0 4 8 15))
(define identicon%
(class object%
(init-field seed)
(field (32-bits (make-bit-stream seed))
(center-patch-shape (list-ref center-patch-shapes (32-bits 2)))
(side-patch-shape (list-ref patch-shapes (32-bits 4)))
(side-rotation (* -90 (32-bits 2)))
(corner-patch-shape (list-ref patch-shapes (32-bits 4)))
(corner-rotation (* -90 (32-bits 2)))
(color (make-object color% (* 8 (32-bits 5)) (* 8 (32-bits 5)) (* 8 (32-bits 5))))
(center-inversion (zero? (32-bits 1)))
(side-inversion (zero? (32-bits 1)))
(corner-inversion (zero? (32-bits 1))))
(define (get-patch-size dc)
(define identicon-size
(let-values (((width height) (send dc get-size)))
(min width height)))
(/ identicon-size 3))
(define (make-patch size shape inversion)
(make-object patch% size shape (if inversion "white" color) (if inversion color "white")))
(define/public (draw dc)
(define patch-size (get-patch-size dc))
(define center-patch (make-patch patch-size center-patch-shape center-inversion))
(define side-patch (make-patch patch-size side-patch-shape side-inversion))
(define corner-patch (make-patch patch-size corner-patch-shape corner-inversion))
(send side-patch rotate side-rotation)
(send corner-patch rotate corner-rotation)
(send dc clear)
(send dc set-smoothing 'smoothed)
(send dc set-pen "black" 1 'transparent)
(send corner-patch draw-rotating dc '((0 0) (2 0) (2 2) (0 2)))
(send side-patch draw-rotating dc '((1 0) (2 1) (1 2) (0 1)))
(send center-patch draw dc 1 1))
(define/public (on-bitmap size)
(let ((bitmap (make-object bitmap% size size)))
(draw (new bitmap-dc% (bitmap bitmap)))
bitmap))
(define/public (save-to-file file-path size)
(send (on-bitmap size)
save-file
(expand-user-path file-path)
(file-extension->kind file-path)))
(define/public (display-in-frame size)
(define (make-frame-dc)
(let* ((frame (new frame% (label "Identicon") (width size) (height size)))
(canvas (new canvas% (parent frame))))
(send frame show #t)
(sleep/yield 1)
(send canvas get-dc)))
(draw (make-frame-dc)))
(super-new)))
(define (random-identicon)
(make-object identicon% (random 4294967087)))
(define (save-random-identicon)
(let* ((identicon (random-identicon))
(file-path (string-append "0x" (number->string (get-field seed identicon) 16) ".png")))
(send identicon save-to-file file-path 30)
file-path))
(define (display-random-identicon)
(send (random-identicon) display-in-frame 300))
(provide/contract
[identicon% class?]
[random-identicon (-> (is-a?/c identicon%))]
[save-random-identicon (-> string?)]
[display-random-identicon (-> void?)])