(ns glimmer-gfx.raster "Software rasterizer over an int-array of 0xRRGGBB. Nothing here knows what a window is -- and nothing here calls into Java. aget/aset/int-array/abs are all clojure.core; the ^ints/^long hints are hints, not calls." (:require [clojure.string :as str])) (defrecord Buf [^ints px ^long w ^long h]) (defn buf [w h] (->Buf (int-array (* w h)) w h)) (defn clear [{:keys [^ints px]} ^long c] (let [c (int c)] (dotimes [i (alength px)] (aset px i c)))) (defn px! [{:keys [^ints px ^long w ^long h]} ^long x ^long y ^long c] (when (and (>= x 0) (< x w) (>= y 0) (< y h)) (aset px (unchecked-add (unchecked-multiply y w) x) (int c)))) (defn rect! "Filled, clipped." [{:keys [^ints px ^long w ^long h]} x0 y0 rw rh c] (let [x1 (min w (+ x0 rw)) y1 (min h (+ y0 rh)) c (int c)] (loop [y (max 0 y0)] (when (< y y1) (let [row (unchecked-multiply y w)] (loop [x (max 0 x0)] (when (< x x1) (aset px (unchecked-add row x) c) (recur (inc x))))) (recur (inc y)))))) (defn line! "Bresenham. ponytail: no AA -- add Wu's if the jaggies bother you." [b x0 y0 x1 y1 c] (let [dx (abs (- x1 x0)) dy (- (abs (- y1 y0))) sx (if (< x0 x1) 1 -1) sy (if (< y0 y1) 1 -1)] (loop [x x0 y y0 err (+ dx dy)] (px! b x y c) (when-not (and (= x x1) (= y y1)) (let [e2 (* 2 err)] (recur (if (>= e2 dy) (+ x sx) x) (if (<= e2 dx) (+ y sy) y) (cond-> err (>= e2 dy) (+ dy) (<= e2 dx) (+ dx)))))))) (defn poly! "Closed wireframe through [x y] points. Vector art is lines, so this is the whole of it. ponytail: outline only -- a filled polygon needs a scanline pass, add one when something actually has to be solid." [b pts c] (doseq [[[x0 y0] [x1 y1]] (map vector pts (concat (rest pts) [(first pts)]))] (line! b (int x0) (int y0) (int x1) (int y1) c))) ;; ------------------------------------------------------------------ 3x5 font ;; Authored as strings so a glyph is readable and fixable in place. (def ^:private glyphs (->> ["A.#.|#.#|###|#.#|#.#" "B##.|#.#|##.|#.#|##." "C.##|#..|#..|#..|.##" "D##.|#.#|#.#|#.#|##." "E###|#..|##.|#..|###" "F###|#..|##.|#..|#.." "G.##|#..|#.#|#.#|.##" "H#.#|#.#|###|#.#|#.#" "I###|.#.|.#.|.#.|###" "J..#|..#|..#|#.#|.#." "K#.#|#.#|##.|#.#|#.#" "L#..|#..|#..|#..|###" "M#.#|###|###|#.#|#.#" "N##.|#.#|#.#|#.#|#.#" "O.#.|#.#|#.#|#.#|.#." "P##.|#.#|##.|#..|#.." "Q.#.|#.#|#.#|###|.##" "R##.|#.#|##.|#.#|#.#" "S.##|#..|.#.|..#|##." "T###|.#.|.#.|.#.|.#." "U#.#|#.#|#.#|#.#|.##" "V#.#|#.#|#.#|#.#|.#." "W#.#|#.#|###|###|#.#" "X#.#|#.#|.#.|#.#|#.#" "Y#.#|#.#|.#.|.#.|.#." "Z###|..#|.#.|#..|###" "0###|#.#|#.#|#.#|###" "1.#.|##.|.#.|.#.|###" "2##.|..#|.#.|#..|###" "3##.|..#|.#.|..#|##." "4#.#|#.#|###|..#|..#" "5###|#..|##.|..#|##." "6.##|#..|###|#.#|###" "7###|..#|.#.|.#.|.#." "8###|#.#|###|#.#|###" "9###|#.#|###|..#|##." " ...|...|...|...|..." "....|...|...|...|.#." ":...|.#.|...|.#.|..." "-...|...|###|...|..." "/..#|..#|.#.|#..|#.." "%#.#|..#|.#.|#..|#.#" "(.#.|#..|#..|#..|.#." ")#..|.#.|.#.|.#.|#.."] (map (fn [s] [(first s) (->> (subs s 1) (remove #{\|}) (partition 3) (mapv str/join))])) (into {}))) (def ^:const glyph-w 4) ; 3px + 1px gap (defn text! "Draws s at (x,y), scaled. Unknown chars render blank." ([b x y s c] (text! b x y s c 2)) ([b x y s c k] (let [s (str/upper-case (str s))] (dotimes [i (count s)] (when-let [g (glyphs (nth s i))] (dotimes [row 5] (dotimes [col 3] (when (= \# (nth (g row) col)) (rect! b (+ x (* k (+ (* i glyph-w) col))) (+ y (* k row)) k k c))))))))) (defn text-w ^long [s ^long k] (* k glyph-w (count s)))