Skip to content

Instantly share code, notes, and snippets.



Last active Aug 29, 2015
What would you like to do?
Experimenting with some basic image dithering, in the style of 10 PRINT (see also, the idea was inspired by _bitcraft's_ OpenProcessing version:
(ns enchilada.image-dither
(:require-macros [cljs.core.async.macros :refer [go]])
[enchilada :refer [canvas ctx value-of canvas-size proxy-request]]
[cljs.core.async :as async :refer [<!]]
[dataview.loader :refer [fetch-image]]
[jayq.core :refer [show]]
[big-bang.core :refer [big-bang]]
[ :refer [client-coords]]
[monet.canvas :refer [draw-image]]))
(def images
; TODO: move into
(defn touch-coords [event]
(when-let [touch-object (.-changedTouches event)]
(client-coords (aget touch-object 0))))
(defn resize [dest src {:keys [w h]}]
(let [ctx (.getContext dest "2d")
temp-canvas (.createElement js/document "canvas")
temp-ctx (.getContext temp-canvas "2d")]
(set! (.-width temp-canvas) (.-width src))
(set! (.-height temp-canvas) (.-height src))
(draw-image temp-ctx src {:x 0 :y 0})
(draw-image ctx temp-canvas
{:sx 0 :sy 0 :sw (.-width temp-canvas) :sh (.-height temp-canvas)
:dx 0 :dy 0 :dw w :dh h })
; TODO remove temp-canvas?
(defn sample [src new-width]
(let [ratio (/ (.-height src) (.-width src))
new-height (* ratio new-width)
dest (.createElement js/document "canvas")]
(set! (.-width dest) new-width)
(set! (.-height dest) new-height)
(resize dest src {:w new-width :h new-height})))
(defn rotate [coll]
(vec (next coll))
(first coll)))
(defn constrain [amt low high]
(< amt low) low
(> amt high) high
:else amt))
(defn handle-mousemove [event {:keys [width] :as world-state}]
(let [mouse-x (first (or (touch-coords event) (client-coords event)))]
(assoc world-state :m (constrain (/ (* 130 (- mouse-x 200)) (* 2 width)) 1 129))))
(defn next-resolution [event {:keys [resolutions width image] :as world-state}]
(let [n (first resolutions)]
:n n
:d (/ width n)
:sample (sample image n)
:resolutions (rotate resolutions)))))
(defn next-effect [event {:keys [effects] :as world-state}]
(if (= (.-keyCode event) 67) ; 'C' key
(assoc world-state :effects (rotate effects))
(defn brightness [pixels x y w]
(let [offset (* 4 (+ (* y w) x))
r (aget pixels offset)
g (aget pixels (inc offset))
b (aget pixels (+ offset 2))]
(/ (+ r g b) 3)))
(defn brightness! [pixels x y w value]
(let [offset (* 4 (+ (* y w) x))]
(aset pixels offset value)
(aset pixels (inc offset) value)
(aset pixels (+ offset 2) value)
(aset pixels (+ offset 3) 0xff)))
(defn diagonal [value dx dy d m]
(let [value (- 0xff value)
z (if (< (mod value (* 2 m)) m)
(/ (* (+ dx dy 1) 0xff) d)
(/ (* (- (+ dx d) dy) 0xff) d))]
(if (> value (+ (* 2 (Math/abs (- z 0xff) 2)))) 0x00 0xff)))
(defn inverse [value dx dy d m]
(- 0xff value))
(defn threshold [value dx dy d m]
(if (> value (* 2 m)) 0x00 0xff))
(defn solarize [value dx dy d m]
(let [a 1
b -4
c 4
x (/ value 0xff)]
(* 0xff (+ a (* b x) (* c x x)))))
(defn render [{:keys [m d n sample image ratio width effects] :as world-state}]
(let [sample-ctx (.getContext sample "2d")
sample-data (.-data (.getImageData sample-ctx 0 0 n (inc (Math/floor (* n ratio)))))
pixels (.getImageData ctx 0 0 width (* width ratio))
pixel-data (.-data pixels)
effect (first effects)]
(dotimes [y (inc (Math/floor (* n ratio)))]
(dotimes [x n]
(let [value (brightness sample-data x y n)
x (* x d)
y (* y d)]
(dotimes [dy d]
(dotimes [dx d]
(+ x dx)
(+ y dy)
(effect value dx dy d m)))))))
(.putImageData ctx pixels 0 0)))
(let [width 400
img (<! (fetch-image (proxy-request (rand-nth images))))
sample (sample img width)
initial-state (next-resolution nil
{:m 6
:width width
:ratio (/ (.-height img) (.-width img))
:image img
:resolutions [50 100 50 25 20 25]
:effects [diagonal identity inverse solarize]})]
(set! (.-height (.get canvas 0)) (.-height sample))
(show canvas)
(draw-image ctx sample {:x width :y 0})
(render initial-state)
;:event-target canvas
:initial-state initial-state
:on-keydown next-effect
:on-mousedown next-resolution
:on-mousemove handle-mousemove
:to-draw render)))
  • Move the mouse horizontally to change threshold point
  • Click the mouse to alter the resolution
  • Press the 'C' key to cycle between [10-Print, Greyscale, Inverse, Solarize]
  • Refresh the page for a different image (chosen at random)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.