Long story short: downscale using a standard downscaling algorithm. Then multiply the value in each element by originalArea/scaledArea
. This is what Rahul meant in the comments.
Why that works:
You can think of every output pixel as a (weighted or not) average of a neighbourhood of the source pixel. And as we know:
average = sum / count
Now, for the caveats.
Broadly, there are 3 types of downscaling algorithms:
- Ones that simply do resampling with some kernel
1.1. Ones that lowpass first
1.2. Ones that don't
- Pyramid downscaling
- Algorithms like the one in "Genuine Fractals" which are intended for detail-preserving upscaling, but could conceivably be abused for downscaling
Since "3." is not intended for downscaling, it's not useful for your case.
The problem with "1.2." is that, normally a small (e.g. 5x5) resampling kernel is used, which means that only a few of the source pixels are taken into account. In image resizing applications, this can manifest as aliasing. In your application, it can manifest as inaccuracy.
This problem with "1.2." can be fixed by using a big resampling kernel. I believe that's what the ImageMagick library used to do. E.g. taking a good-quality kernel like Lanczos and stretching it by x and y. Or you could go ahead and use a big box kernel like you describe in the question. Which one is appropriate depends on your exact application, but if you're dead set on using a box kernel, use that. The algorithm you described in the question would work fine, and I believe that on a CPU there is no better one (except splitting this one into threads), for the reason that any algorithm would need at least N (number of pixels) operations to take into account all the pixels, and your algorithm does a tiny amount of work for each of these N pixels.
But on the GPU there is a better algorithm that takes full advantage of the GPU's strong parallelism. It's called "parallel reduction", you can find info on it based on that. Since it's widely documented, I'll just sketch the algorithm:
Iteratively downscale the image by a factor of 2 by x and y until you get to your desired size.
"1.1." would probably also suit your needs, but I won't comment on it for now, as it's not much better than the other options.