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)

asked Jul 22, 2015 at 4:45
\$\endgroup\$
9
  • \$\begingroup\$ What image formats do we have to handle? Specifically, can we choose to handle only PPM? \$\endgroup\$ 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\$ Commented Jul 22, 2015 at 5:10
  • \$\begingroup\$ @Maltysen I've added a 240x140 example. Hopfully that's small enough \$\endgroup\$ 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\$ 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+'
answered Jul 22, 2015 at 18:24
\$\endgroup\$
1
  • 4
    \$\begingroup\$ That's smart! +1 \$\endgroup\$ 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
answered Jul 22, 2015 at 5:17
\$\endgroup\$
5
  • \$\begingroup\$ You might want to specify the image type, if any. \$\endgroup\$ Commented Jul 22, 2015 at 6:39
  • 1
    \$\begingroup\$ @isaacg good point. It takes anything. \$\endgroup\$ Commented Jul 22, 2015 at 6:44
  • \$\begingroup\$ How does it take anything, does it decode the jpeg to a bitmap? \$\endgroup\$ 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\$ 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\$ 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.

answered Jul 22, 2015 at 16:51
\$\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.
answered Jul 22, 2015 at 5:17
\$\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');

answered Jul 22, 2015 at 18:38
\$\endgroup\$
6
  • 3
    \$\begingroup\$ Hey, I like that it's runnable directly in your answer because it's HTML + JS :) +1. \$\endgroup\$ 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))
answered Jul 22, 2015 at 12:15
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Why not use input() for taking the path? It'll save you 20 bytes. \$\endgroup\$ Commented Jul 22, 2015 at 13:15
  • \$\begingroup\$ Thanks! I've managed to save 11 bytes though. \$\endgroup\$ Commented Jul 22, 2015 at 13:36
  • \$\begingroup\$ You only need one import, import scipy. Change m.imread to misc.imread. \$\endgroup\$ 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\$ 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\$ 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"
answered Jul 23, 2015 at 10:57
\$\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.

answered Jul 23, 2015 at 17:29
\$\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('')
answered Jul 23, 2015 at 4:50
\$\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.

answered Nov 24, 2015 at 20:36
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.