I just started messing around with Java's BufferedImage
. I had an idea to try to encode a message into a picture by coloring each pixel a certain color that corresponds to a certain character.
Here's the above paragraph as a picture:
It took about 18 minutes to generate. Note the black parts. Those are spaces.
And here's the Wikipedia article for Steganography:
I needed a way of enumerating colors to match colors to characters.
To do this, I decided to treat the RGB color as a 3-digit, base-255 number, then just increment each channel as though I was counting (increment the rightmost channel until it gets to 255, then make it 0 and increment the channel to the left). I used this for a previous project, and although it's slow, it was acceptable.
(defn inc-permutation
"\"Counts\" arbitrary symbols.
Example: (inc-permutation \\a \\c #(char (inc (int %))) [\\a \\b \\b])
returns [\\a \\b \\c], then [\\a \\c \\a], [\\a \\c \\b], [\\a \\c \\c], [\\b \\a \\a], [\\b \\a \\lein pomb]...
Quite slow."
[first-symbol last-symbol inc-f current-permutation]
(let [current-ones (last current-permutation)
carry? (= current-ones last-symbol)
overflown? (empty? current-permutation)
last-i (dec (count current-permutation))]
(cond
carry? (conj
(inc-permutation first-symbol last-symbol inc-f (subvec current-permutation 0 last-i))
first-symbol)
overflown? []
:else (assoc current-permutation last-i (inc-f current-ones)))))
It works, but it seems like a hack more than anything.
I calculated that to cover the entire range of colors with a char, I would need to increment each color 174540 times * (- (int (character) 32)
. At 88ms per increment, it will take a little over 8 seconds to increment a \~
to [253 3 19]
, which for a message of any length is unacceptable.
Below is the full code. The last method, color-of-char
, is where it's all tied together.
(ns bits.image.color-encode.conversion-helper)
(def min-char-code 32)
(def max-char-code "inclusive" 127)
(def total-colors (int (Math/pow 255 3)))
(def color-code-mult (int (inc (/ total-colors (- max-char-code min-char-code)))))
(def starting-color [0 0 0])
(defn inc-permutation ...)
(defn advance-color [color color-step]
(let [f #(g/inc-permutation 0 255 inc %)]
(if (= color-step 1)
(f color)
(reduce (fn [c _] ; Faster than `iterate`
(f c))
color
(range color-step)))))
(defn color-of-char [chr]
(let [code (- (int chr) min-char-code)]
(advance-color [0 0 0]
(* code color-code-mult))))
(color-of-char \a)
-> [173 29 13]
, but it takes an average of 5.879696 seconds to get there (according to Criterium).
Is there anything I can do to speed up the current way? Any general comments would be welcome too.
Is there a better way to "increment a RGB color"? Alternatively, is there a way to get some nth color, that isn't
O(n)
?
Edit:
I managed to get a mostly working constant time conversion algorithm written that relies on rem
and division. It's O(n)
with respect to the number of places in the color, which is much better. Here's this entire question as a picture:
It took about a quarter second to generate. The darker parts are low value characters like punctuation and numbers. They probably correspond to the code.
I'm still interested in any feedback though, since the new solution is still a work in progress.