I'm attempting to calculate the average color of an image in Scala, where "average" is defined as the redSum/numpixels, greenSum/numpixels, blueSum/numpixels .
Here is the code I am using to calculate the average color in a rectangular region of an image (the Raster).
// A raster is an abstraction of a piece of an image and the underlying
// pixel data.
// For instance, we can get a raster than is of the upper left twenty
// pixel square of an image
def calculateColorFromRaster(raster:Raster): Color = {
  var redSum = 0
  var greenSum = 0
  var blueSum = 0
  val minX = raster.getMinX()
  val minY = raster.getMinY()
  val height = raster.getHeight()
  val width = raster.getWidth()
  val numPixels = height * width
  val numChannels = raster.getNumBands() 
  val pixelBuffer = new Array[Int](width*height*numChannels)
  val pixels = raster.getPixels(minX,minY,width,height,pixelBuffer)
  // pixelBuffer now filled with r1,g1,b1,r2,g2,b2,...
  // If there's an alpha channel, it will be r1,g1,b1,a1,r2,... but we skip the alpha
  for (i <- 0 until numPixels) {
    val redOffset = numChannels * i
    val red = pixels(redOffset)
    val green = pixels(redOffset+1)
    val blue = pixels(redOffset+2)
    redSum+=red
    greenSum+=green
    blueSum+=blue
  }
  new Color(redSum / numPixels, greenSum / numPixels, blueSum / numPixels)
}
Is there a more idiomatic Scala way of summing up over the different interleaved arrays? Some way to get a projection over the array that iterates over every 4th element? I'm interested in any expertise the Stack Overflow community can provide.
pixels.grouped(3) will return an Iterator[Array[Int]] of 3-element arrays. So
val pixelRGBs = pixels.grouped(3)
val (redSum, greenSum, blueSum) = 
  pixelRGBs.foldLeft((0, 0, 0)) {case ((rSum, gSum, bSum), Array(r, g, b)) => (rSum + r, gSum + g, bSum + b)}
new Color(redSum / numPixels, greenSum / numPixels, blueSum / numPixels)
UPDATE: To deal with both 3 and 4 channels, I would write
pixels.grouped(numChannels).foldLeft((0, 0, 0)) {case ((rSum, gSum, bSum), Array(r, g, b, _*)) => (rSum + r, gSum + g, bSum + b)}
_* here basically means "0 or more elements". See "Matching on Sequences" in http://programming-scala.labs.oreilly.com/ch03.html
This is insane overkill for this problem, but I do a lot of partitioned reductions over datasets, and have built some utility functions for it. The most general of them is reduceBy, which takes a collection (actually a Traversable), a partition function, a mapping function, and a reduction function, and produces a map from partitions to reduced/mapped values.
  def reduceBy[A, B, C](t: Traversable[A], f: A => B, g: A => C, reducer: (C, C) => C): Map[B, C] = {
    def reduceInto(map: Map[B, C], key: B, value: C): Map[B, C] =
      if (map.contains(key)) {
        map + (key -> reducer(map(key), value))
      }
      else {
        map + (key -> value)
      }
    t.foldLeft(Map.empty[B, C])((m, x) => reduceInto(m, f(x), g(x)))
  }
Given that heavy machinery, your problem becomes
val sumByColor:Map[Int, Int] = reduceBy(1 until numPixels, (i => i%numChannels), (i=>pixel(i)), (_+_))
return Color(sumByColor(0)/numPixels, sumByColor(1)/numPixels, sumByColor(2)/numPixels)
Stand mute before the awesome power of higher order programming.
This is a great question, since I think the solution you have provided is the idiomatic solution! The imperative model really fits this problem. I tried to find a simple functional solution that reads well, but I could not do it.
I think the one with pixels.grouped(3) is pretty good, but I am not sure it is better than the one you have.
My own "non imperative" solution involves defining a case class with the + operator/method:
import java.awt.image.Raster
import java.awt.Color
def calculateColorFromRaster(raster:Raster): Color = {
  val minX = raster.getMinX()
  val minY = raster.getMinY()
  val height = raster.getHeight()
  val width = raster.getWidth()
  val numPixels = height * width
  val numChannels = raster.getNumBands()
  val pixelBuffer = new Array[Int](width*height*numChannels)
  val pixels = raster.getPixels(minX,minY,width,height,pixelBuffer)
  // pixelBuffer now filled with r1,g1,b1,r2,g2,b2,...
  // If there's an alpha channel, it will be r1,g1,b1,a1,r2,... but we skip the alpha
  // This case class is only used to sum the pixels, a real waste of CPU!
  case class MyPixelSum(r: Int, g: Int, b: Int){
    def +(sum: MyPixelSum) = MyPixelSum(sum.r +r, sum.g + g, sum.b + b)
  }
  val pixSumSeq= 0 until numPixels map((i: Int) => {
    val redOffset = numChannels * i
    MyPixelSum(pixels(redOffset), pixels(redOffset+1),pixels(redOffset+2))
  })
  val s = pixSumSeq.reduceLeft(_ + _)
  new Color(s.r / numPixels, s.g / numPixels, s.b / numPixels)
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With