Skip to main content
1 of 9
Carcigenicate
  • 16.3k
  • 3
  • 35
  • 80

Incrementing colors

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.

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 incrementing 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).

  1. Is there anything I can do to speed up the current way? Any general comments would be welcome too.

  2. 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)?

Carcigenicate
  • 16.3k
  • 3
  • 35
  • 80