1

I'm trying to write a function in ClojureScript, which returns the average RGBA value of a given ImageData Object.

In JavaScript, implementations for this problem with a "for" or "while" loop are very fast. Within milliseconds they return the average of e.g. 4000 x 4000 sized ImageData objects.

In ClojureScript my solutions are not approximately as fast, sometimes the browser gives up yielding "stack trace errors".

However the fastest one I wrote until now is this one:

(extend-type js/Uint8ClampedArray
      ISeqable
      (-seq [array] (array-seq array 0)))

(defn average-color [img-data]
  (let [data (.-data img-data)
        nr (/ (count data) 4)]
    (->> (reduce (fn [m v] (-> (update-in m [:color (rem (:pos m) 4)] (partial + v))
                               (update-in [:pos] inc)))
                 {:color [0 0 0 0] :pos 0}
                 data)
         (:color)
         (map #(/ % nr)))))

Well, unfortunately it works only upt to values around 500x500, which is not acceptable.

I'm asking myself what exactly is the problem here. What do I have to attend in order to write a properly fast average-color function in ClojureScript.

Anton Harald
  • 5,772
  • 4
  • 27
  • 61
  • Great question. I would look at the source for [imagez](https://github.com/mikera/imagez). I suspect @mikera who wrote that library and is very active on SO will be the best one to answer it for you. But the answer will be in say a function such as get-pixels from here: https://github.com/mikera/imagez/blob/develop/src/main/clojure/mikera/image/core.clj. I suspect the problem is that you are not compiling everything ahead of time and so all the Clojure stuff is carried along. You want it to be a Java program at runtime. – Chris Murphy Jan 20 '16 at 15:21

1 Answers1

1

The problem is that the function you have defined is recursive. I am not strong in clojurescript so I will not tell you haw to fix the problem in code but in concept.

You need to break the problem into smaller recursive units. So reduce a pixel row to get a result for each row, then reduce the row results. This will prevent the recursion from overflowing the call stack in javascript.

As for the speed, That will depend on how accurate you want the result to be, I would use a random sample selecting 10% of pixels randomly and using the average of that result.

You could also just use the hardware and scale the image by half, render it with smoothing on and then halving again until you have one pixel and use the value of that pixel. That will give you a pixel value average and is very fast but only does a value mean, not a photon mean.

I will point out that the value of the RGB channels are logarithmic and represent the square root of the photon count captured (for photo) or emitted (by screen). Thus the mean of the pixel values is much lower than the mean of the photon count. To get the correct mean you must get the mean of the square of each channel and then get the square root of the mean to bring it back to the logarithmic scale that is used for the RGB values.

Blindman67
  • 51,134
  • 11
  • 73
  • 136