# Paint Starry Night, objectively, in 1kB of code

Note: Anders Kaseorg has been awarded the accept for now, to draw attention to his great answer, but the challenge is by no means over! There is still a 400 point bounty in the offering for anyone who takes the top score without using built-in compression.

Below is a 386x320 png representation of van Gogh's Starry Night.

Your goal is to reproduce this image as closely as possible, in no more than 1024 bytes of code. For the purposes of this challenge, the closeness of images is measured by the squared differences in RGB pixel values, as explained below.

This is . Scores are calculated using the validation script below. The lowest score wins.

Your code must obey the following restrictions:

• It must be a complete program
• It must output an image in a format that can be read by the validation script below, running on my machine. The script uses Python's PIL library, which can load a wide variety of file formats, including png, jpg and bmp.
• It must be completely self-contained, taking no input and loading no files (other than importing libraries, which is allowed)
• If your language or library includes a function that outputs Starry Night, you are not allowed to use that function.
• It should run deterministically, producing the same output every time.
• The dimensions of the output image must be 386x320
• For the avoidance of doubt: valid answers must use programming languages as per the usual PPCG rules. It must be a program that outputs an image, not just an image file.

It is likely that some submissions will themselves be generated by code. If this is the case, please include in your answer the code that was used to produce your submission, and explain how it works. The above restrictions only apply to the 1kB image-generating program that you submit; they don't apply to any code used to generate it.

## Scoring

To calculate your score, take your output image and the original above and convert the RGB pixel values to floating point numbers ranging from 0 to 1. The score of a pixel is (orig_r-img_r)^2 +(orig_g-img_g)^2 + (orig_b-img_b)^2, i.e. the squared distance in RGB space between the two images. The score of an image is the sum of the scores of its pixels.

Below is a Python script that performs this calculation - in the case of any inconsistency or ambiguity, the definitive score is the one calculated by that script running on my machine.

Note that the score is calculated based on the output image, so if you use a lossy format that will affect the score.

The lower the score the better. The original Starry Night image would have a score of 0. In the astronomically unlikely event of a tie, the answer with the most votes will determine the winner.

## Bonus objectives

Because the answers were dominated by solutions using built-in compression, I awarded a series of bounties to answers that use other techniques. The next one will be a bounty of 400 points, to be awarded if and when an answer that does not use built-in compression takes the top place overall.

The previously awarded bonus bounties were as follows:

• A 100 point bounty was awarded to nneonneo's answer, for being the highest-scoring answer that did not use built-in compression at the time. It had 4852.87 points at the time it was awarded. Honourable mentions go to 2012rcampion, who made a valiant attempt to beat nneonneo using an approach based on Voronoi tesselation, scoring 5076 points, and to Sleafar, whose answer was in the lead until near the end, with 5052 points, using a similar method to nneonneo.

• A 200 point bounty was awarded to Strawdog's entry. This was awarded for being an optimization-based strategy that took the lead among non-built-in-compression answers and held it for a week. It scored 4749.88 points using an impressively clever method.

## Scoring/validation script

The following Python script should be placed in the same folder as the image above (which should be named ORIGINAL.png) and run using a command of the form python validate.py myImage.png.

from PIL import Image
import sys

orig = Image.open("ORIGINAL.png")
img  = Image.open(sys.argv[1])

if img.size != orig.size:
print("NOT VALID: image dimensions do not match the original")
exit()

w, h = img.size

orig = orig.convert("RGB")
img = img.convert("RGB")

score = 0

for x in range(w):
for y in range(h):
orig_r, orig_g, orig_b = orig_pix[x,y]
img_r, img_g, img_b = img_pix[x,y]
score += (img_r-orig_r)**2
score += (img_g-orig_g)**2
score += (img_b-orig_b)**2

print(score/255.**2)


Technical note: Objective measures of image similarity are a tricky thing. In this case I've opted for one that's easy for anyone to implement, in full knowledge that much better measures exist.

var QUESTION_ID=69930,OVERRIDE_USER=21034;function answersUrl(e){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(e,s){return"https://api.stackexchange.com/2.2/answers/"+s.join(";")+"/comments?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),answers_hash=[],answer_ids=[],e.items.forEach(function(e){e.comments=[];var s=+e.share_link.match(/\d+/);answer_ids.push(s),answers_hash[s]=e}),e.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){e.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),e.has_more?getComments():more_answers?getAnswers():process()}})}function getAuthorName(e){return e.owner.display_name}function process(){var e=[];answers.forEach(function(s){var r=s.body;s.comments.forEach(function(e){OVERRIDE_REG.test(e.body)&&(r="<h1>"+e.body.replace(OVERRIDE_REG,"")+"</h1>")});var a=r.match(SCORE_REG);a&&e.push({user:getAuthorName(s),size:+a[2],language:a[1],link:s.share_link})}),e.sort(function(e,s){var r=e.size,a=s.size;return r-a});var s={},r=1,a=null,n=1;e.forEach(function(e){e.size!=a&&(n=r),a=e.size,++r;var t=jQuery("#answer-template").html();t=t.replace("{{PLACE}}",n+".").replace("{{NAME}}",e.user).replace("{{LANGUAGE}}",e.language).replace("{{SIZE}}",e.size).replace("{{LINK}}",e.link),t=jQuery(t),jQuery("#answers").append(t);var o=e.language;/<a/.test(o)&&(o=jQuery(o).text()),s[o]=s[o]||{lang:e.language,user:e.user,size:e.size,link:e.link}});var t=[];for(var o in s)s.hasOwnProperty(o)&&t.push(s[o]);t.sort(function(e,s){return e.lang>s.lang?1:e.lang<s.lang?-1:0});for(var c=0;c<t.length;++c){var i=jQuery("#language-template").html(),o=t[c];i=i.replace("{{LANGUAGE}}",o.lang).replace("{{NAME}}",o.user).replace("{{SIZE}}",o.size).replace("{{LINK}}",o.link),i=jQuery(i),jQuery("#languages").append(i)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;getAnswers();var SCORE_REG=/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+(?:\.\d+))(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/,OVERRIDE_REG=/^Override\s*header:\s*/i;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:400px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"> <div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Score</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table>

• On Windows I had trouble installing the python requirements. A safer option is to use pillow (pip unistall PIL, then pip install pillow) and change the first line to from PIL import Image. – mınxomaτ Jan 23 '16 at 16:17
• @tepples other than going in the opposite direction and not being logarithmic, yes :) – hobbs Jan 24 '16 at 16:11
• I'm surprised that not a single answer has tried working with greyscale output yet. Averaging the channels in each pixel gives a score of something like 2800, and having only to compress a third of the data would introduce less error on top of that. – Martin Ender Jan 28 '16 at 10:16
• @MartinBüttner you could probably do even better by weighting a greyscale image by the average bluish colour of the image. I hadn't thought of this. – Nathaniel Jan 28 '16 at 10:29
• @Nathaniel hasn't been said enough, but this question is so awesome! ;-) – Pierre Arlaud Jan 28 '16 at 10:33

## bash + base64 + bpgdec, 4363.51784698

base64 -d>q<<<QlBH+2AAgwKCQAADkkJQRAHBkJWBFgNwAAABJgGvBdj9qQPRDPJb0JstVKPQw7JUMnA9cHZZuSvN5LfqLUYKsGDdgAcWfhxLa0YsnYwOO0fWZXO4OqHzX5ADymrgGGCv+zW21b35rhCmBtfK6uUHrMjRwckNPtYo1E4n2eU423MvrkdzUdRauwRQWO0pHxzyv3RfvYaKDDWttHEGFg55fZLwwfodyaDvcmOHYk6ekHLviqucjow6vDdg7J6NQXYoZJwfZPThO0u6jfWyWrXToUjq3XBj3NO+IQOjk0z1Zbc+NSd8kgH7a6fVRL6dAg/52jRsMgPf96U4dTbzk9gIEH54ArF/TdTweDp7d3FxOXFiQi8TTPrrTaqfO1QLKFrvOfQv2bIsCUxpGYffO8eqMZZIMGko50F0tWzk5TpZcTr2PqFSF6xbF7tzjJBDoVp3moFXU0kGNPBr7wgBQxwzuQlY+p0oXn3WMsuMfy+c7DUQGP9DvApHSmHNdRyfPNkyxfs0Hzn8KAsZ0Yiaz3ZjdwBuhDk87xYLAtzo9/G/A3Qja381JQynGiLuI9V36vVXTzQjccWbn9q43tcNBooroUD3Kjo6MITCG+69ij5j+X9z0aMs8v7Tpr2m5FS5tR21erm9Wr18BUYu9Oft49ZKgNd3fxi37keDsK1LlsowGrkRVkemvyVE7fznfyxGuWkXz+MfVIzIlQTMJxgaupqhSJgNv05hd/8LkU9PitwYP2S92d/UayjhJScIMaAP7AtyLWhhlrrPhNfdbWG0ermM2/sRGeKxC0sIEsqbiccjtwWkeEwUMZ6Fopm9oXL/a2KDLRAcTC+ef00wTB+m/dk2mql1WtXX44ewjCHAFV7Qvn8NaHB+rBVWskSfvwAwtztwwLRZ4sl9VURj8Iv81Xeg6CjW+0fmkmxEoY8PjeQY2wCB7XTj8vvCM3yPGWmIEEKE95NJmVgFXhLvJ1qOIBa+IclF0zU=;bpgdec q -o w


Evolutionary advancement of yallie's BPG answer, just to lower the score. Not competing for the bounty (in this answer).

Encoding command line userd:

bpgenc -c ycbcr -f 444 -noalpha -q 49 -m 10 -e jctvc ORIGINAL.png -o 1.bpg


I also experimented with bpgenc patched to use x265 in PSNR mode, but it didn't improve results. Also tried usual HEVC and VP9 (without scaling, the latter can't produce small enough file at all).

• Can you embed the resulting image? – curiousdannii Feb 1 '16 at 3:33
• It is visually very similar to other BPG solutions. – Vi. Feb 1 '16 at 8:21

# zsh + FLIF, 4672.71078816

This solution is very similar to @orlp's, and it scores worse...

So why am I even bothering to type this?

Well, this solution has one less dependency: it does not require ImageMagick. Maybe that counts for something :)

Exploiting FLIF's progressive decoding, we create an interlaced FLIF file of a simplified image (to make sure that as much information is squeezed in as possible) and truncate it to fit the available file size. Since the FLIF image has the correct size already (the FLIF decoder will interpolate what was truncated), ImageMagick is not needed to scale the image up.

Here's the script that produces the 1024-byte script that produces the image:

convert ORIGINAL.png -resize x80 -resize 386x320! -depth 6 - | pngquant --speed 1 --ordered -f 25 > i.png
flif i.png o.flif -vvvvvvvvvvvvv -I -T200 -Y -R0 -P0
dd if=o.flif of=o.truncated.flif bs=992 count=1
echo 'exec flif =(tail -n+2 $0) o.png' > genstarry.sh cat o.truncated.flif >> genstarry.sh zsh genstarry.sh  # C, 10299.28 draw.min.c, 250 bytes: #define N getchar() char m[320][386][3];main(){puts("P6 386 320 255");int c=N,i=0;while(c--){int r=N,g=N,b=N,x=N*2,v=N*2,u=N*2,w=N*2,y;for(;x<=v;x++){for(y=u;y<=w;y++){m[y][x][0]=r;m[y][x][1]=g;m[y][x][2]=b;}}}while(i<370560){putchar(*((**m)+i++));}}  image.dat, 149 bytes (hexdump): 0000000 3915 3d41 c100 a000 5043 568e 2bbb 4361 0000010 9050 974e 532b 4765 6389 2a97 5f53 7947 0000020 ba41 4e03 3d3a 439f 07c1 3970 8e5c 886d 0000030 622b 7943 4e96 006d 2563 9ead 706d 6303 0000040 5c38 5684 219b 3a53 8e5c 9256 760e 5c3a 0000050 006d 00bb 5870 a95c 7063 5307 4b3a 63a9 0000060 3ba0 385f 7c5c 9741 6307 5c58 4579 2abb 0000070 6553 8e79 5543 6200 ad25 43c5 31ac 4362 0000080 795c 8b03 530e 7f3a 337c 1b6d 6563 8470 0000090 c12b 6303 000a  Compile draw.min.c into a.out and pipe image.dat into it. This will result in a PPM file on stdout. gcc -std=c99 draw.min.c ./a.out < image.dat > output.ppm  ## Explanation image.dat is a list of rectangles to draw. The first byte is the number of rectangles; the remainder of the file is the actual rectangles. Each rectangle is 7 bytes: the first three are the color as RGB; then the x-coordinate to start at, the x-coordinate to end at, the y-coordinate to start at, and the y-coordinate to end at. These coordinates are multiplied by two so they cover the entire image (thus, one 'pixel' is actually 2x2). draw.min.c simply takes this list and draws each entry into its internal pixmap, then dumps the resulting Portable PixMap file. The data file could also be embedded into the program but it was easier for development to have it separate. This was primarily an experiment in genetic algorithms. I've posted the code I used to generate this file on GitHub. The conclusion I reached is that they're a lot of effort to implement, and if not done right they don't give very good results at all. • +1 for using a genetic algorithm, but to comply with the rules your program must take no input and load no files, so you would have to include the data in the source code. – Nathaniel Jan 26 '16 at 5:28 • +1 as well, I also tried some optimization approach, but based on a Voronoi Diagram (maybe I'll post the results later...) – Marco13 Jan 26 '16 at 10:47 • +1 from me too, we had mostly the same idea :) I didn't look through your code too carefully, but it looks like you also let color vary? I think you'll get better results if you pick the color of each rectangle to minimize the score since you know the target image ahead of time (I just took a mean of each color channel in the original image). – neocpp Jan 26 '16 at 14:32 # C#, 6735.73 1023 bytes My naive C# 4.0 approach, based on a 12x10 image on base64. The PixelOffsetMode.HighQuality makes a huge difference on the result, that's why I spent some bytes on that. using System;using System.Drawing; class P{ static void Main(){ var i=Image.FromStream(new System.IO.MemoryStream(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAYAAACALL/6AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AEdFx8ea7+R2AAAAZVJREFUGNMFwctuElEAgOH/zJwB5sYMUrCNicakpdVaE62JJq6MO5+hG/e+gQ/iG7h14QNoTNy4aexCm0ZZSAu0XAYYYG7MOX6fOHn3Xk/HBVG0JmzUCUKDm3GVTQ7b7Rn9oYsf2hwcVlFZivx14UMx5/FuxOnfHTJtEviKOC5ZZC7akHQOW7w5+kZU7iIPOgb9gUWkHvH0RZNlNKDUDu0gZLXMOX4F3b5mnG2xTtfIcLuFaaQMb3rYy4SK3QZh4vkuWT7j5+8SUVV8/P4M25wj41XMenbG4N+CWzv7NFsNRuOI0fUIr25TNxwyJXh+pLjTDJDTyZrRZQ5acdwxsawVvascLTWev8GpmoQBnJ8P+PDpM/L+XYeoZ5Jcdvny9ZR7D/eoOwXpquTsxwJdJBRlhUqtwYOXJ4gnr9/qIu4yH/4B06Ti7eE2buN4AcowEWWC6woc26ZirJCkI7LFAGlZaDQ6vUIkOVpMiKIUS2rymkdcq2EKifTsa1TdIppkaFWi1ZI8k0ijZMsHIQOSjYEoEvLNlP8c3rfxrVY4fQAAAABJRU5ErkJggg=="))); var d=new Bitmap(386,320); var g=Graphics.FromImage(d); g.PixelOffsetMode=System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; g.DrawImage(i,0,0,386,320); d.Save("d.png");}}  # Python 3, score 5052.495855440216 No builtin compression used. This program creates a 30x25 image from embedded binary data. Each byte encodes one pixel of the image. The most significant 4 bits encode the green channel, the least significant 4 bits encode the blue channel. The red channel is created by copying and scaling the values of the green channel. Finally the image is resized to 386x320 and saved to "a.png". Partial code (see hexdump for complete code): #coding:latin-1 from PIL.Image import * d='''place data from hexdump here''' i=new('RGB',(30,25)) for y in range(25): for x in range(30): t=ord(d[x+y*30]) g=(t>>4)*165/15+26 i.putpixel((x,y),(round(g*75/91),round(g),round((t&0xf)*162/15+21))) i.resize((386,320),BICUBIC).save('a.png')  Hexdump: 00000000: 2363 6f64 696e 673a 6c61 7469 6e2d 310a #coding:latin-1. 00000010: 6672 6f6d 2050 494c 2e49 6d61 6765 2069 from PIL.Image i 00000020: 6d70 6f72 7420 2a0a 643d 2727 2738 9aa9 mport *.d='''8.. 00000030: 6916 5878 165b a98b 595a 7a7b 5a49 3838 i.Xx.[..YZz{ZI88 00000040: 5949 486a 5a7b 6a7b 7b6b 5939 abc7 9c29 YIHjZ{j{{kY9...) 00000050: 3747 3849 aa8a a88a 4a39 3a3a aa99 6b4a 7G8I....J9::..kJ 00000060: 5b49 6b6a aedb caca 8b39 6b7c 4b38 152a [Ikj.....9k|K8.* 00000070: 4a4b 4b3a 5a49 4a4a 296b b9a7 7a49 386c JKK:ZIJJ)k..zI8l 00000080: 7dbc daf9 e6ea cc38 5c5c 4c3a 2656 8749 }......8\\L:&V.I 00000090: 3a4a 4949 485a 4939 3a7b 7b49 3849 5c5c :JIIHZI9:{{I8I\\ 000000a0: 9edb ead3 f9ea dc5b 4b5b 4b36 96b4 6a4b .......[K[K6..jK 000000b0: 5b7b 9caa adac 7b8c 6b48 5a8b 8c6d 8dec [{....{.kHZ..m.. 000000c0: e7d2 f9e9 db8b 4a4a 3a25 5a7a 5b8c 8c8b ......JJ:%Zz[... 000000d0: aa7b 798b 8c7c ae6a 6bca bb8d 8dcc fad3 .{y..|.jk....... 000000e0: d3c4 dc9b 9b6a 4b58 588d 9dbc 9c9c 485b .....jKXX.....H[ 000000f0: 7b7b 8c8c 7b8c 6baa cd9d 7d9b cbe9 f9ea {{..{.k...}..... 00000100: bc8b adad 9c9b 239d ac9c bc37 8b9b 8c9d ......#....7.... 00000110: 8c9d 8c6b 6c6c 6c8d 7c8c acbd bcad 9dac ...klll.|....... 00000120: 6a8c 9b46 229d 9c8a ba6b 8c8c ad8b 8dad j..F"....k...... 00000130: ae6a 7b5a 7c8c 6b8d 7d9e 8d7b 8cbd bdbf .j{Z|.k.}..{.... 00000140: 8b34 236b 365a 6a8c 9d8c 6b8c 8cad 8c7c .4#k6Zj...k....| 00000150: 9c9b 8c8c 8c9d 8e9e 8d8c cc8c 8c9c 7912 ..............y. 00000160: 2258 5848 6b9d ae9c 9b6b 7b6b 7b9b 9c6b "XXHk....k{k{..k 00000170: 9d9d 7c9e 9dae ddec ddcc 7cba b811 1157 ..|.......|....W 00000180: 7b8c adbd 9d9d 9c9c 9c8c 7c7c 9c9d 9c9e {.........||.... 00000190: 7b9c edec eded dd6b 8dba b911 1135 7bcc {......k.....5{. 000001a0: eced de6b 6a7b 7b6b 7b7b ad9d ac7c 8bdc ...kj{{k{{...|.. 000001b0: ddcd ddab 786a 6a7d 5811 1135 7bcc ebda ....xjj}X..5{... 000001c0: bc8c 7b7c 7c7b 6a8b ac8c 8c9c dcdc cc9c ..{||{j......... 000001d0: 6801 139a ab6c 4611 0122 599a eeef ac8b h....lF.."Y..... 000001e0: 9cab 9bab ccbb ccbc bcbc bcac ab45 1313 .............E.. 000001f0: 149b cbbd 4601 0111 4659 9cab b9ba abaa ....F...FY...... 00000200: aaab 7a8a 8a9a 9b8b 5835 1437 5959 599c ..z.....X5.7YYY. 00000210: 9cbd 6801 1111 2322 abbc 7b8b 7a8b 8b69 ..h...#"..{.z..i 00000220: 3838 3815 1436 364a 5c5c 7d8c 7d7b 6a9b 888..66J\\}.}{j. 00000230: 8c8b 1101 1123 1133 595a 3736 5949 594a .....#.3YZ76YIYJ 00000240: 4827 385a 5b6a 5b6a 8c9d 7b6a 365a 4936 H'8Z[j[j..{j6ZI6 00000250: 1101 0101 1111 1214 1548 4847 5937 3749 .........HHGY77I 00000260: 4835 4646 5757 8975 7766 2425 2411 1101 H5FFWW.uwf$%$... 00000270: 0101 1111 1225 2546 3536 5768 6947 3534 .....%%F56WhiG54 00000280: 4657 5758 7968 6746 2424 2512 1121 1111 FWWXyhgF$$%..!.. 00000290: 0111 1112 3525 4646 5624 2547 3635 2425 ....5%FFV$%G65$% 000002a0: 3658 5757 4668 2336 3511 0111 1101 1111 6XWWFh#65....... 000002b0: 1111 1312 4423 6935 1302 3336 4534 1224 ....D#i5..36E4.$
000002c0: 3614 2423 2544 4411 1111 1111 2111 1101  6.$#%DD.....!... 000002d0: 1222 2345 3422 2302 0114 4536 7835 4546 ."#E4"#...E6x5EF 000002e0: 5647 3253 4321 1111 0111 1101 0101 1111 VG2SC!.......... 000002f0: 2223 2333 4345 3423 2336 1546 4634 1133 "##3CE4##6.FF4.3 00000300: 5354 4454 3211 2211 1101 1101 1122 3433 STDT2."......"43 00000310: 3423 3434 4556 3534 2423 1323 3355 2727 4#44EV54$#.#3U''
00000320: 270a 693d 6e65 7728 2752 4742 272c 2833  '.i=new('RGB',(3
00000330: 302c 3235 2929 0a66 6f72 2079 2069 6e20  0,25)).for y in
00000340: 7261 6e67 6528 3235 293a 0a20 666f 7220  range(25):. for
00000350: 7820 696e 2072 616e 6765 2833 3029 3a0a  x in range(30):.
00000360: 2020 743d 6f72 6428 645b 782b 792a 3330    t=ord(d[x+y*30
00000370: 5d29 0a20 2067 3d28 743e 3e34 292a 3136  ]).  g=(t>>4)*16
00000380: 352f 3135 2b32 360a 2020 692e 7075 7470  5/15+26.  i.putp
00000390: 6978 656c 2828 782c 7929 2c28 726f 756e  ixel((x,y),(roun
000003a0: 6428 672a 3735 2f39 3129 2c72 6f75 6e64  d(g*75/91),round
000003b0: 2867 292c 726f 756e 6428 2874 2630 7866  (g),round((t&0xf
000003c0: 292a 3136 322f 3135 2b32 3129 2929 0a69  )*162/15+21))).i
000003d0: 2e72 6573 697a 6528 2833 3836 2c33 3230  .resize((386,320
000003e0: 292c 4249 4355 4249 4329 2e73 6176 6528  ),BICUBIC).save(
000003f0: 2761 2e70 6e67 2729 0a                   'a.png').


Image:

# Python 3 (no built in compression) 7147.31

While not a terribly competitive answer, I had the idea of using a pseudo-grid optimized to each color. It has the nice property that it looks like abstract stained glass.

The drawing algorithm is this -- for each of the colors, define six "vertical" lines (from a point on the top edge to a point on the bottom edge) and six "horizontal" lines (from a point on the left edge to a point on the right edge). Assign each pixel a value (i,j) where i is the number of horizontal lines above it and j is the number of vertical lines to the left of it. For each of the 49 (i,j) pairs, define a value from 0 to 255 for that color channel. Each channel gets its own grid and set of 49 color values. A Gaussian blur of 3 pixels is applied afterwards. This fits into 1020 bytes.

import base64, numpy, PIL.Image, PIL.ImageFilter
j="0123456789"
q={a:b for b,a in zip(j+"()[], ",j+"abcdef")}
def z(x,y,u,d,w):
return sum([x>(a+(b-a)*(y/w)) for a,b in zip(u,d)])
exec("s,h,w,u,d,l,r,c="+"".join(q[t] for t in base64.binascii.b2a_hex(base64.b85decode(b'rsE*vhU~(Y;}hd3<0;W;db71LK}S;}YeLW!}L)Gv?>yE-l{7H-0b<Fdiw67LHNg!xiC};Zfsa<9;KJ>==#~E*CDIN8y0o?8%-WULZaWBVJAHC+I#><>EQwSKh)>;Wy)MBu*iI5Ix?^BOVo=79JoT7#<l7>?!76CE^YjZWb-<77i)mS>a{kY3GgJ%va%2=RRrSCgvVT=nf=Kli_6HQ0R^=bjLLhUZ=rE)iZw=ROx+A)XU%5q=WrUK1Xb;#1*M;u7UvC*cR-gW@LPIpHJa9%11);v(ZdY2qGu=AG;iUJ;IP=q?ftFy;;!PGRUSP~@H<8CtLE-2@I6HXB>L*ouM=Uxzw6F!&bK6vR4W$B&~UKlQB=L5{8R3B8FyVIQju3tq9u{5~o)tbK=q>Dq;X~m$;YQ&C<DPKgDd9rlY2ifSN#t&0;xOSz;S}SJRpD+i;UwseBOWAvH{ufFjuJi<K2DM12jWgu;WpupGT~<9ZVS-;VI!T;Z)&4;T7Q%=1to')).decode("utf-8")))
PIL.Image.fromarray(numpy.array([[[c[i][(s+1)*z(x,y,u[i],d[i],h)+z(y,x,l[i],r[i],w)] for i in range(3)] for x in range(w)] for y in range(h)]).astype("uint8")).filter(PIL.ImageFilter.GaussianBlur(3)).save("o.png")


I used simulated annealing to determine the best parameters, and base85 encoded them into the python program. I think there's a lot of tuning that could be done here -- for example, I encode the parameters by printing a huge tuple, stripping out the spaces, converting the characters 0-9()[], into hex digits, then encoding that in b85. Probably not the best way to do it.