20
\$\begingroup\$

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)

\$\endgroup\$
9
  • \$\begingroup\$ What image formats do we have to handle? Specifically, can we choose to handle only PPM? \$\endgroup\$
    – Dennis
    Commented Jul 22, 2015 at 4:56
  • \$\begingroup\$ 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. \$\endgroup\$
    – Maltysen
    Commented Jul 22, 2015 at 5:10
  • \$\begingroup\$ @Maltysen I've added a 240x140 example. Hopfully that's small enough \$\endgroup\$
    – Downgoat
    Commented Jul 22, 2015 at 5:14
  • \$\begingroup\$ Should we always round down? In the first example, the 95.6..., which you have rounded to 95 in the specified output. \$\endgroup\$
    – Dennis
    Commented Jul 22, 2015 at 5:16
  • 4
    \$\begingroup\$ PS 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. \$\endgroup\$ Commented Jul 22, 2015 at 5:55

10 Answers 10

22
\$\begingroup\$

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+'
\$\endgroup\$
1
  • 4
    \$\begingroup\$ That's smart! +1 \$\endgroup\$
    – Maltysen
    Commented Jul 22, 2015 at 19:34
19
\$\begingroup\$

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
\$\endgroup\$
5
  • \$\begingroup\$ You might want to specify the image type, if any. \$\endgroup\$
    – isaacg
    Commented Jul 22, 2015 at 6:39
  • 1
    \$\begingroup\$ @isaacg good point. It takes anything. \$\endgroup\$
    – Maltysen
    Commented Jul 22, 2015 at 6:44
  • \$\begingroup\$ How does it take anything, does it decode the jpeg to a bitmap? \$\endgroup\$
    – Alec Teal
    Commented Jul 22, 2015 at 12:06
  • 3
    \$\begingroup\$ @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. \$\endgroup\$
    – Bakuriu
    Commented Jul 22, 2015 at 18:58
  • \$\begingroup\$ @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! \$\endgroup\$
    – rayryeng
    Commented Jul 22, 2015 at 19:27
10
\$\begingroup\$

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('https://i.sstatic.net/dkShg.jpg');
>> ['#' sprintf('%02x',fix(sum(sum(I))*3/numel(I)))]

ans =

#53715f

Second Image

>> I=imread('https://i.sstatic.net/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.

\$\endgroup\$
7
\$\begingroup\$

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.
\$\endgroup\$
7
\$\begingroup\$

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.sstatic.net/dkShg.jpg');

f('http://crossorigin.me/https://i.sstatic.net/V5VAR.jpg');

\$\endgroup\$
6
  • 3
    \$\begingroup\$ Hey, I like that it's runnable directly in your answer because it's HTML + JS :) +1. \$\endgroup\$
    – rayryeng
    Commented Jul 22, 2015 at 18:49
  • \$\begingroup\$ Can't you replace new Image with Image()? \$\endgroup\$ Commented Jul 22, 2015 at 21:48
  • \$\begingroup\$ @IsmaelMiguel TypeError: Constructor Image requires 'new' \$\endgroup\$ Commented Jul 22, 2015 at 21:50
  • \$\begingroup\$ Crap. But you can create W='width' and H='height' and use i[H] or i[W] \$\endgroup\$ Commented Jul 22, 2015 at 21:52
  • 1
    \$\begingroup\$ @IsmaelMiguel That uses more characters \$\endgroup\$ Commented Jul 22, 2015 at 21:55
6
\$\begingroup\$

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))
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Why not use input() for taking the path? It'll save you 20 bytes. \$\endgroup\$
    – Kade
    Commented Jul 22, 2015 at 13:15
  • \$\begingroup\$ Thanks! I've managed to save 11 bytes though. \$\endgroup\$
    – Trang Oul
    Commented Jul 22, 2015 at 13:36
  • \$\begingroup\$ You only need one import, import scipy. Change m.imread to misc.imread. \$\endgroup\$
    – Kade
    Commented Jul 22, 2015 at 13:41
  • \$\begingroup\$ Doesn't work without importing misc, NameError: name 'misc' is not defined. Tried from scipy import*, doesn't work either. \$\endgroup\$
    – Trang Oul
    Commented Jul 22, 2015 at 13:51
  • 2
    \$\begingroup\$ @TrangOul What about from scipy import sum, misc as m? You save when you use sum as well, then. \$\endgroup\$
    – matsjoyce
    Commented Jul 22, 2015 at 15:04
3
\$\begingroup\$

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"
\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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('')
\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.