Permalink
Browse files

initial mosaic code

  • Loading branch information...
1 parent bd740f5 commit 351916b0ade33ba01e69f160f98510c8c47d0e5a @andrewcooke committed Apr 28, 2012
View
@@ -1,5 +1,12 @@
-pom.xml
+/pom.xml
*jar
-/lib/
-/classes/
-.lein-deps-sum
+/lib
+/classes
+/native
+/.lein-failures
+/checkouts
+/.lein-deps-sum
+.idea
+*.iml
+.bashrc
+*~
View
@@ -1,53 +1,5 @@
-particl
-=======
+At some point this may be a complete email app...
-Taking email back.
+but for now it's starting as a replacement for my old parti.cl GAE service.
-What is this?
--------------
-
-At the moment, very little.
-
-I am looking for a new project to work on - one that could be important in
-ways that matter to me. Looking around, I see quite a bit of discussion about
-how email should be improved, more discussion about how bad it is for Google
-to control so much data, and various more general concerns about privacy.
-
-So this is an attempt to make a cross-platform email client that gives people
-control over their email again. That's the initial medium-term goal. The
-longer term goal is to extend this to other social media. All while adding
-decent crypto built around unsafe (but practical - the ssh compromise when
-connecting to an unknown site) public key crypto.
-
-Tell me more!
--------------
-
-OK, here are various random notes:
-
- - It's web based. There's no need, for a local mail client, but it's what I
- know, and it keeps options open for future use (eg when IPV6 means
- everything is online).
-
- - It uses Clojure and clojurescript. The client will be "smart". Because
- that's the language I'm happiest with right now, and it lets me leverage
- Java libraries for a lot of the hard work.
-
- - It will grab email from Google via IMAP and save it to local disk,
- optionally deleting.
-
- - When email is sent, a public key is included in headers. Public keys
- received are kept in a database. Email sent to someone with a known public
- key will be automatically encrypted.
-
- - It will use a local database (think mairix) to provide fast search.
-
- - The client will be simple, but will have a plugin architecture. And I
- guess the client too. Am reading open source architecture book right now.
- Need to think about lessons from that.
-
- - Need to choose a licence. I prefer GPL but perhaps MIT will help growth?
-
-That's all for now,
-
-Andrew
View
@@ -2,4 +2,5 @@
:description "FIXME: write description"
:repositories {"lib" ~(str (.toURI (java.io.File. "repo")))}
:dependencies [[org.clojure/clojure "1.3.0"]
+ [org.clojure/math.numeric-tower, "0.0.1"]
[ar.com.hjg/pngj "0.90.0"]])
View
@@ -1 +1,70 @@
-(ns cl.parti.hsl)
+(ns cl.parti.hsl
+ (:use (cl.parti random))
+ (:use clojure.math.numeric-tower))
+
+(defn clip [x]
+ (cond
+ (< x 0) 0
+ (> x 1) 1
+ :else x))
+
+(defn fold [x]
+ (mod x 1))
+
+; https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
+(defn rgb [[h s l]]
+ (let [h (* 6 (fold h))
+ s (clip s)
+ l (clip l)
+ chroma (* s (- 1 (abs (dec (* 2 l)))))
+ x (* chroma (- 1 (abs (dec (mod h 2)))))
+ [r g b]
+ (cond
+ (< h 1) [chroma, x, 0]
+ (< h 2) [x, chroma, 0]
+ (< h 3) [0, chroma, x]
+ (< h 4) [0, x, chroma]
+ (< h 5) [x, 0, chroma]
+ :else [chroma, 0, x]
+ )
+ m (- l (/ chroma 2))]
+ [(+ r m) (+ g m) (+ b m)]))
+
+; https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
+(defn hsl [[r g b]]
+ (let [r (clip r)
+ g (clip g)
+ b (clip b)
+ mx (max r g b)
+ mn (min r g b)
+ chroma (- mx mn)
+ h (/ (cond
+ (= 0 chroma) 0
+ (= mx r) (/ (- g b) chroma)
+ (= mx g) (+ 2 (/ (- b r) chroma))
+ :else (+ 4 (/ (- r g) chroma))
+ ) 6)
+ l (/ (+ mn mx) 2)
+ s
+ (if (= 0 chroma)
+ 0
+ (/ chroma (- 1 (abs (dec (* 2 l))))))]
+ [h s l]))
+
+(defn lighten [k [h s l]]
+ [h s (clip (* k l))])
+
+(defn rotate [k [h s l]]
+ [(fold (+ k h)) s l])
+
+; correlate lightness and colour shifts
+(defn colourblind [state]
+ (let [[k state] (range-closed 8 state)
+ [x state] (range-closed 0.1 state)]
+ [#(lighten k (rotate k %)) state]))
+
+(def white [0. 0. 1.])
+(def black [0. 0. 0.])
+(def red (hsl [1. 0. 0.]))
+(def green (hsl [0. 1. 0.]))
+(def blue (hsl [0. 0. 1.]))
View
@@ -1 +1,54 @@
-(ns cl.parti.mosaic)
+(ns cl.parti.mosaic
+ (:use (cl.parti random)))
+
+; a mosaic is a [n a [row]] tuple, where row is [hsl] (one per column)
+; and a is +/1 for the axis of symmetry
+
+(defn mosaic [n a bg]
+ [n a (vec (repeat n (vec (repeat n bg))))])
+
+(defn square [n a r1 r2]
+ (let [xlo (int (* n (min r1 r2)))
+ xhi (int (* n (max r1 r2)))
+ [ylo yhi] (if (> 0 a) [xlo xhi] [(- n xhi) (- n xlo)])]
+ [xlo xhi ylo yhi]))
+
+(defn transform-square [[n a rows] [t [r1 r2]]]
+ (let [[xlo xhi ylo yhi] (square n a r1 r2)
+ rows (reduce t rows (for [x (range xlo xhi)
+ y (range ylo yhi)] [x y]))]
+ [n a rows]))
+
+(defn parameters [transform-factory state]
+ (lazy-seq (let [[r1 state] (uniform-open state)
+ [r2 state] (uniform-open state)
+ [t state] (transform-factory state)]
+ (cons [t [r1 r2]] (parameters transform-factory state)))))
+
+(defn repeated-transform [mosaic n transform-factory state]
+ (reduce transform-square mosaic
+ (take n (parameters transform-factory state))))
+
+(defn bracket-interpose [sep col]
+ (concat [sep] (interpose sep col) [sep]))
+
+(defn no-interpose [sep col]
+ col)
+
+(defn print-mosaic [print-colours convert [n a rows] scale [colour width]]
+ (let [size (+ (* n scale) (* (inc n) width))
+ horizontal (repeat width (repeat size (convert colour)))
+ vertical (repeat width colour)
+ assemble (if (> width 0) bracket-interpose no-interpose)]
+ (print-colours size
+ (apply concat
+ (assemble horizontal
+ (for [row rows]
+ (repeat scale
+ (apply concat
+ (assemble vertical
+ (for [col row]
+ (repeat scale (convert col))))))))))))
+
+(defn print-identity [size colours]
+ colours)
View
@@ -1 +1,27 @@
-(ns cl.parti.png)
+(ns cl.parti.png
+ (:use (cl.parti hsl))
+ (:use clojure.math.numeric-tower)
+ (:import ar.com.hjg.pngj.PngWriter)
+ (:import ar.com.hjg.pngj.ImageInfo)
+ (:import ar.com.hjg.pngj.ImageLine)
+ (:import ar.com.hjg.pngj.ImageLineHelper))
+
+
+; size is the number of pixels on a square edge
+; colours is nested lists of rgb colours
+; so colours = [row]; row = [col]; col = [r g b]
+(defn make-print-png [os]
+ (fn [size colours]
+ ; 8 bits, no alpha, no grayscale, not indexed
+ (let [info (ImageInfo. size size 8 Boolean/FALSE Boolean/FALSE Boolean/FALSE)
+ writer (PngWriter. os info)
+ line (ImageLine. info)]
+ (doseq [[i row] (map-indexed vector colours)]
+ (doseq [[j [r g b]] (map-indexed vector row)]
+ (println size i j r g b)
+ (ImageLineHelper/setPixelRGB8 line j r g b))
+ (.writeRow writer line i))
+ (.end writer))))
+
+(defn png-8-bit [hsl]
+ (map (fn [x] (int (* 255 x))) (rgb hsl)))
View
@@ -1 +1,61 @@
-(ns cl.parti.random)
+(ns cl.parti.random
+ (:use clojure.math.numeric-tower)
+ (:import java.security.MessageDigest))
+
+; the secure random signature in java isn't clear on repeatability, so
+; we use an explicit (sha-512) hash here. we care about "cosmetic"
+; randomness (once the hash has been generated), but guaranteed
+; repeatability.
+
+; we have 512 bits, 64 bytes, of initial state. we need to generate
+; random values between 0 and 1 with a byte's resolution, so have 64 initial
+; independent values. to extend that we will circulate the bytes, adding
+; a simple rotation and xor to avoid immediate duplication.
+
+(defn queue [vals]
+ (reduce conj clojure.lang.PersistentQueue/EMPTY vals))
+
+(defn state [text]
+ (let [hash (. MessageDigest getInstance "SHA-512")
+ bytes (.digest hash (.getBytes text))]
+ (queue bytes)))
+
+(defn rotate-byte [n b]
+ (let [b (if (< b 0) (+ b 0x100) b)
+ left (bit-shift-left (bit-and b (dec (expt 2 n))) (- 8 n))
+ right (bit-shift-right b n)]
+ (unchecked-byte (bit-or left right))))
+
+(defn scramble-byte [b]
+ (unchecked-byte (bit-xor 0x55 (rotate-byte 3 b))))
+
+(defn byte-stream [s]
+ (lazy-seq
+ (let [old (peek s)
+ new (scramble-byte old)]
+ (cons old (byte-stream (conj (pop s) new))))))
+
+; [-127 127]
+(defn unbiased-byte [stream]
+ (let [b (first stream)
+ stream (rest stream)]
+ (if (= -128 b) (recur stream) [b stream])))
+
+; [0 255]
+(defn whole-byte [stream]
+ [(+ 128 (first stream)) (rest stream)])
+
+; [0.0 1.0)
+(defn uniform-open [stream]
+ [(/ (+ 128 (first stream)) 256.0) (rest stream)])
+
+; [0.0 1.0]
+(defn uniform-closed [stream]
+ [(/ (+ 128 (first stream)) 255.0) (rest stream)])
+
+; [lo hi]
+(defn range-closed
+ ([hi stream] (range-closed (- hi) hi stream))
+ ([lo hi stream]
+ (let [[x stream] (uniform-closed stream)]
+ [(+ lo (* x (- hi lo))) stream])))
View
@@ -1 +1,13 @@
-(ns cl.parti.test.hsl)
+(ns cl.parti.test.hsl
+ (:use (cl.parti hsl))
+ (:use clojure.test))
+
+(deftest test-hsl-to-rgb
+ (is (= [1 1 1] (rgb white)))
+ (is (= [0 0 0] (rgb black)))
+ (is (= [1 0 0] (rgb red)))
+ (is (= [0 1 0] (rgb green)))
+ (is (= [0 0 1] (rgb blue)))
+ )
+
+(run-tests)
@@ -1 +1,31 @@
-(ns cl.parti.test.mosaic)
+(ns cl.parti.test.mosaic
+ (:use (cl.parti hsl mosaic random))
+ (:use clojure.test))
+
+(deftest test-mosaic
+ (is (= [2 1 [[[0. 0. 1.] [0. 0. 1.]] [[0. 0. 1.] [0. 0. 1.]]]] (mosaic 2 1 white))))
+
+(deftest test-transform
+ (let [m (mosaic 2 1 red)
+ s (byte-stream (queue [1]))
+ m (repeated-transform m 1 colourblind s)]
+ ; BAD - all red still!
+ (is (= m [2 1 [[[0. 1. 0.5] [0. 1. 0.5]] [[0. 1. 0.5] [0. 1. 0.5]]]]))))
+
+(deftest test-assemble-no-border
+ (is (= [[:x :x :x :x] [:x :x :x :x] [:x :x :x :x] [:x :x :x :x]]
+ (print-mosaic print-identity identity (mosaic 2 1 :x ) 2 [nil 0])))
+ (is (= [[:x :x] [:x :x]]
+ (print-mosaic print-identity identity (mosaic 1 1 :x ) 2 [nil 0])))
+ (is (= [[:x]]
+ (print-mosaic print-identity identity (mosaic 1 1 :x ) 1 [nil 0])))
+ (is (= [[:y :y :y] [:y :x :y] [:y :y :y]]
+ (print-mosaic print-identity identity (mosaic 1 1 :x ) 1 [:y 1])))
+ (is (= [[:y :y :y :y :y] [:y :y :y :y :y] [:y :y :x :y :y] [:y :y :y :y :y] [:y :y :y :y :y]]
+ (print-mosaic print-identity identity (mosaic 1 1 :x ) 1 [:y 2])))
+ )
+
+(deftest test-bracket
+ (is (= [[1] [2] [1] [3] [1]] (bracket-interpose [1] [[2] [3]]))))
+
+(run-tests)
View
@@ -1 +1,13 @@
-(ns cl.parti.test.png)
+(ns cl.parti.test.png
+ (:use clojure.java.io)
+ (:use (cl.parti hsl mosaic random png))
+ (:use clojure.test))
+
+
+(deftest test-write
+ (let [print-png (make-print-png (output-stream "/tmp/mosaic-test.png"))
+ m (mosaic 3 1 red)
+ border [black 3]]
+ (print-mosaic print-png png-8-bit m 10 border)))
+
+(run-tests)
@@ -1 +1,31 @@
-(ns cl.parti.test.random)
+(ns cl.parti.test.random
+ (:use (cl.parti random))
+ (:use clojure.test))
+
+
+(deftest test-state
+ (let [s (state "hello world")]
+ (is (= (peek s) 48))))
+
+(deftest test-rotate
+ (is (= 2 (rotate-byte 1 4)))
+ (is (= 1 (rotate-byte 2 4)))
+ (is (= -128 (rotate-byte 3 4)))
+ (is (= -86 (rotate-byte 1 0x55)))
+ (is (= 0x55 (rotate-byte 1 -86))))
+
+(defn index-of [x s]
+ (first (first (filter
+ (fn [[i y]] (= x y))
+ (map-indexed (fn [a b] [a b]) s)))))
+
+(deftest test-byte-stream
+ (let [b (byte-stream (queue [1 2 3]))]
+ (is (= 1 (first b)))
+ (is (= [1 2 3 117 21 53 -5 -9 -13 42] (take 10 b))))
+ (let [b (byte-stream (queue [1]))
+ a (first b)
+ b (rest b)]
+ (is (= 7 (index-of a b)))))
+
+(run-tests)

0 comments on commit 351916b

Please sign in to comment.