Average Color of an Image

21

6

Average color of an image

Scientists have been able to determine the average color of the universe but in how many bytes can we find the average color on an image?

Your task

Your input will be a single image which you will need to find the average of the colors in the image and output a hex color-string (#??????). The image can be any of the following formats

  • JPEG/JFIF
    • JPEG 2000
  • TIFF
  • GIF
  • BMP
  • PNG
  • PNM
    • PPM

The input can also be taken as a URL/URI to the image.

Build-in functions which calculate averages or sample the image at once such as ImageMeasurements are not allowed.

Examples

Results will differ slightly depending on how you calculate the average and which color models you use. I've added RGB and LCH (HSV) values for the images below.

Sample 1 output: #53715F RGB, may also be #3B7D3D LCH (HSV)


Sample 2 output: #8B7D41 RGB, #96753C LCH (HSV)

Downgoat

Posted 2015-07-22T04:45:24.453

Reputation: 27 116

What image formats do we have to handle? Specifically, can we choose to handle only PPM? – Dennis – 2015-07-22T04:56:13.223

Can I have a smaller test case please? My script is very slow, and while I will run it on the large case, I don't to waste that time if it is wrong. Or even just the script you calculated it with. – Maltysen – 2015-07-22T05:10:28.360

@Maltysen I've added a 240x140 example. Hopfully that's small enough – Downgoat – 2015-07-22T05:14:01.420

Should we always round down? In the first example, the 95.6..., which you have rounded to 95 in the specified output. – Dennis – 2015-07-22T05:16:40.010

@Dennis Yeah, you can just truncate off (round down) the decimal – Downgoat – 2015-07-22T05:17:49.703

Must the colourspace used for the average be uniform (e.g. L*a*b*) or can it be the colourspace of the input image (i.e. YUV for JPEG, etc.)? What about gamma? – Peter Taylor – 2015-07-22T05:53:17.490

4PS There's no point posting a question in the sandbox unless you're going to leave it there for at least 24 hours, so that people in other time zones can see it, and realistically you need to give it 72 hours because not everyone checks the sandbox obsessively. – Peter Taylor – 2015-07-22T05:55:51.240

@PeterTaylor I've updated the question, your results will obviously depend on which color model you use. – Downgoat – 2015-07-22T06:11:35.060

You may want to add in the fact that you're assuming the images are unsigned 8-bit integer per channel... or 24-bit RGB images. There are some images... like in TIF... especially in medical image stacks where the bit precision is larger... 16-bit or 32-bit perhaps. – rayryeng - Reinstate Monica – 2015-07-22T17:39:58.297

Answers

19

Pyth - 23 22 19 18 16 bytes

Transposes to get all channels, then sums, divides, and hexifies each. Finishes by concatenating and prepending a #.

+\#sm.H/sdldCs'z

Takes an image file name (any type) from stdin and outputs to stdout. VERY SLOW.

+               String concat
 \#             "#"
 s              String concat all channels
 m              Map
  .H            Hex string
    /  ld       Divided by length of channel
     sd         Sum of channel
  C             Transpose to separate into channels
   s            Concatenate all rows
    'z          Read image with filename input

Sample run

>>>pyth avg.pyth 
V5VAR.jpg
#8b7d41

Maltysen

Posted 2015-07-22T04:45:24.453

Reputation: 25 023

You might want to specify the image type, if any. – isaacg – 2015-07-22T06:39:42.807

1@isaacg good point. It takes anything. – Maltysen – 2015-07-22T06:44:34.530

How does it take anything, does it decode the jpeg to a bitmap? – Alec Teal – 2015-07-22T12:06:32.317

3

@AlecTeal According to the documentation Pyth checks whether the file is an image and automatically converts it into a bitmap. Searching the GitHub repository it looks like it uses the PIL library to handle images. Look here for the exact source.

– Bakuriu – 2015-07-22T18:58:15.530

@Bakuriu - I didn't upvote this answer because I wasn't aware how Pyth handled images so it looked a bit fishy to me... but now that you provided insight there, this gets my vote. Thanks for the clarification! – rayryeng - Reinstate Monica – 2015-07-22T19:27:06.653

22

Bash, 46 bytes

ImageMagick scales image to one pixel which contains average of the colors in the image then outputs it as text.

convert $1 -scale 1x1\! txt:-|egrep -o '#\w+'

SteelRaven

Posted 2015-07-22T04:45:24.453

Reputation: 741

4That's smart! +1 – Maltysen – 2015-07-22T19:34:27.683

10

MATLAB - 68 bytes

The image is read in with imread combined with uigetfile to open up a GUI to choose the image you want to load in. The assumption with this code is that all images are RGB, and to calculate the average colour, we sum over each channel individually then divide by as many elements as there are in one channel, which is the total number of pixels in the RGB image (numel(I)) divided by 3. Because the average can possibly generate floating point values, a call to fix is required to round the number down towards zero. sprintf combined with the hexadecimal formatting string (%x) is used to print out each integer value in the average into its hex equivalent. However, the 02 is there to ensure that an extra 0 is padded to the left should the average value for any channel be less than 16*.

I=imread(uigetfile);
['#' sprintf('%02x',fix(sum(sum(I))*3/numel(I)))]

Sample Runs

The nice thing about imread is that you can read in images directly from URLs. As a reproducible example, let's assume you have downloaded the images on your computer and have run the above code... but for demonstration, I'll read the images directly from Code Golf:

First Image

>> I=imread('http://i.stack.imgur.com/dkShg.jpg');
>> ['#' sprintf('%02x',fix(sum(sum(I))*3/numel(I)))]

ans =

#53715f

Second Image

>> I=imread('http://i.stack.imgur.com/V5VAR.jpg');
>> ['#' sprintf('%02x',fix(sum(sum(I))*3/numel(I)))]

ans =

#8b7d41

*Note: This was a collaborative effort made by StackOverflow users on the MATLAB and Octave chat room.

rayryeng - Reinstate Monica

Posted 2015-07-22T04:45:24.453

Reputation: 1 521

7

CJam, 27 bytes

'#[q~]5>3/_:.+\,f/"%02X"fe%

This read a PPM image from STDIN.

CJam has no built-in image processing, so this code expects an ASCII Portable PixMap (magic number P3) with full 24-bit palette (maximum value 255) and no comments.

Test run

$ cjam avg.cjam < dkShg.ppm 
#53715F

How it works

'#     e# Push that character.
[q~]   e# Evaluate the input and collect the results in an array.
5>     e# Discard the first first results (Pi, 3, width, height, range).
3/     e# Split into chunks of length 3 (RGB).
_:.+   e# Push a copy and add across the columns (RGB).
\,f/   e# Divide each sum by the length of the array (number of pixels).
"%02X" e# Push that format string (hexadecimal integer, zero-pad to two digits).
fe%    e# Format each integer, using the format string.

Dennis

Posted 2015-07-22T04:45:24.453

Reputation: 196 637

7

HTML5 + JavaScript (ES6), 335 bytes

This is not going to win but I had fun doing it anyways.

Uses the HTML5 Canvas API. Input is a URL of a CORS-enabled image.

f=(u,i=new Image)=>{i.crossOrigin='';i.src=u;i.onload=e=>{x=(c=document.createElement('canvas')).getContext('2d');a=w=c.width=i.width;a*=h=c.height=i.height;x.drawImage(i,0,0);for(d=x.getImageData(m=0,0,w,h).data,r=[0,0,0];m<d.length;m+=4){r[0]+=d[m];r[1]+=d[m+1];r[2]+=d[m+2]}console.log('#'+r.map(v=>(~~(v/a)).toString(16)).join``)}}

Demo

As it is ES6, it currently only works in Firefox and Edge.

f = (u,i = new Image) => {
  i.crossOrigin = '';
  i.src = u;
  i.onload = e => {
    x = (c = document.createElement('canvas')).getContext('2d');
    a = w = c.width = i.width;
    a *= h = c.height = i.height;
    x.drawImage(i, 0, 0);
    for (d = x.getImageData(m = 0, 0, w, h).data, r = [0, 0, 0]; m < d.length; m += 4) {
      r[0] += d[m]
      r[1] += d[m + 1];
      r[2] += d[m + 2];
    }
    console.log('#' + r.map(v => (~~(v/a)).toString(16)).join``)
  }
}

// Snippet stuff
console.log = x => document.body.innerHTML += x + '<br>';

f('http://crossorigin.me/https://i.stack.imgur.com/dkShg.jpg');

f('http://crossorigin.me/https://i.stack.imgur.com/V5VAR.jpg');

rink.attendant.6

Posted 2015-07-22T04:45:24.453

Reputation: 2 776

3Hey, I like that it's runnable directly in your answer because it's HTML + JS :) +1. – rayryeng - Reinstate Monica – 2015-07-22T18:49:29.377

Can't you replace new Image with Image()? – Ismael Miguel – 2015-07-22T21:48:57.210

@IsmaelMiguel TypeError: Constructor Image requires 'new' – rink.attendant.6 – 2015-07-22T21:50:05.467

Crap. But you can create W='width' and H='height' and use i[H] or i[W] – Ismael Miguel – 2015-07-22T21:52:04.987

1@IsmaelMiguel That uses more characters – rink.attendant.6 – 2015-07-22T21:55:41.173

I really can't see anything to squeeze out of there. – Ismael Miguel – 2015-07-22T22:03:17.447

6

Python [3] + SciPy, 144 133 121

Loads pixel data, sums for each channel, divides by size*, formats. Values are rounded towards zero.

*size = width * height * channels, thus multiplied by 3

from scipy import misc,sum
i=misc.imread(input())
print('#'+(3*'{:2x}').format(*sum(i.astype(int),axis=(0,1))*3//i.size))

Trang Oul

Posted 2015-07-22T04:45:24.453

Reputation: 656

1Why not use input() for taking the path? It'll save you 20 bytes. – Kade – 2015-07-22T13:15:49.947

Thanks! I've managed to save 11 bytes though. – Trang Oul – 2015-07-22T13:36:52.333

You only need one import, import scipy. Change m.imread to misc.imread. – Kade – 2015-07-22T13:41:20.560

Doesn't work without importing misc, NameError: name 'misc' is not defined. Tried from scipy import*, doesn't work either. – Trang Oul – 2015-07-22T13:51:57.597

2@TrangOul What about from scipy import sum, misc as m? You save when you use sum as well, then. – matsjoyce – 2015-07-22T15:04:49.423

3

R, 90 bytes

rgb(matrix(apply(png::readPNG(scan(,"")),3,function(x)sum(x*255)%/%length(x)),nc=3),m=255)

Path to PNG file is read from STDIN. Package png needs to be installed.

Step-by-step:

#Reads path from stdin and feeds it to function readPNG from package png
p = png::readPNG(scan(,""))
#The result is a 3d matrix (1 layer for each color channel) filled with [0,1] values
#So next step, we compute the mean on each layer using apply(X,3,FUN)
#after moving the value to range [0,255] and keeping it as an integer.
a = apply(p,3,function(x)sum(x*255)%/%length(x))
#The result is then moved to a matrix of 3 columns:
m = matrix(a, nc=3)
#Which we feed to function rgb, while specifying that we're working on range [0,255]
rgb(m, m=255)

# Example:
rgb(matrix(apply(png::readPNG(scan(,"")),3,function(x)sum(x*255)%/%length(x)),nc=3),m=255)
# 1: ~/Desktop/dkShg.png
# 2: 
# Read 1 item
# [1] "#53715F"

plannapus

Posted 2015-07-22T04:45:24.453

Reputation: 8 610

2

Cobra - 371

@ref 'System.Numerics'
use System.Drawing
use System.Numerics
class P
    def main
        i,d=Bitmap(Console.readLine?''),BigInteger
        r,g,b,c=d(),d(),d(),d()
        for x in i.width,for y in i.height,r,g,b,c=for n in 4 get BigInteger.add([r,g,b,c][n],d([(p=i.getPixel(x,y)).r,p.g,p.b,1][n]))
        print'#'+(for k in[r,g,b]get Convert.toString(BigInteger.divide(k,c)to int,16)).join('')

Οurous

Posted 2015-07-22T04:45:24.453

Reputation: 7 916

2

C, 259 Bytes

Takes a PPM file with no comments.

double*f,r,g,b;x,y,z,i;char*s="%d %d %d";main(a,_){(a-2)?(feof(f)?0:(fscanf(f,s,&x,&y,&z),r+=(x-r)/i,g+=(y-g)/i,b+=(z-b)/i++,main(0,0))):(f=fopen(((char**)_)[1],"r"),fscanf(f,"%*s%*d%*d%*d"),r=g=b=0.,i=1,main(0,0),printf(s,(int)r,(int)g,(int)b),fclose(f));}

Process

Initial code:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    FILE *f = fopen(argv[1],"r");
    int w,h,d,x,y,z,i;
    double r,g,b;
    fscanf(f,"%*s %d %d %d",&w,&h,&d);//get width, height, depth, ignore P6
    r = g = b = 0.0; //zero r, g, and b totals
    for (i=1; i<=w*h; ++i) {
        fscanf(f,"%d %d %d",&x,&y,&z);//get next pixel
        r+=(x-r)/i;//update averages
        g+=(y-g)/i;
        b+=(z-b)/i;
    }
    printf("%d %d %d",(int)r,(int)g,(int)b);//print result
    fclose(f);
    return 0;
}

Trim variables and remove loop:

double r,g,b;
FILE *f;
int i;
int main(int argc, char *argv[])
{
    if (argc==2) { // {./me} {file.ppm}
        f = fopen(argv[1],"r");
        fscanf(f,"%*s%*d%*d%*d");//drop info
        r = g = b = 0.0;
        i = 1;
        main(0,0);//begin load loop
        printf("%d %d %d",(int)r,(int)g,(int)b);
        fclose(f)
    } else {
        if (feof(f)) return 0;
        fscanf(f,"%d%d%d",&x,&y,&z);
        r+=(x-r)/i;
        g+=(y-g)/i;
        b+=(z-b)/i;
        i++;
        main(0,0);
    }
    return 0;
}

From there I combined the various statements into a single return statement. Removed it and any other extraneous type info, renamed variables, and cut the whitespace.

LambdaBeta

Posted 2015-07-22T04:45:24.453

Reputation: 2 499

2

Java, 449 447 446 430 426 bytes

import java.awt.*;interface A{static void main(String[]a)throws Exception{java.awt.image.BufferedImage i=javax.imageio.ImageIO.read(new java.io.File(new java.util.Scanner(System.in).nextLine()));int r=0,g=0,b=0,k=0,x,y;for(x=0;x<i.getWidth();x++)for(y=0;y<i.getHeight();k++){Color c=new Color(i.getRGB(x,y++));r+=c.getRed();g+=c.getGreen();b+=c.getBlue();}System.out.printf("#%06X",0xFFFFFF&new Color(r/k,g/k,b/k).getRGB());}}

Thanks to this answer over on stack overflow for the String.format trick.

SuperJedi224

Posted 2015-07-22T04:45:24.453

Reputation: 11 342