HTML/JavaScript
Score: 10855.83 8000.55 5999.90
Notes:
Due to rendering differences, the score may vary slightly depending on the browser or GPU.
You have to right-click > Save Image As to save the canvas data as an image, but that's the only interaction that's required.
Attempt #1 (Jan 2016): 10855.83
For my first attempt, I had misread the prompt and thought that a higher score was better. Oops.
I used polygons for this attempt because I was focusing more on keeping the total number of shapes low rather than squeezing in as many simple shapes as possible.
All things considered, it's pretty crazy how just 4-5 shapes can look so much like a famous painting.
For the colors, I used GIMP to select certain areas and find their average. In particular, the color picker tool and "layer difference" feature were very helpful.
<canvas width="386" height="320" id="c">
<script>
var canvas = document.getElementById("c");
var context = canvas.getContext("2d");
context.fillStyle="#2c3e84";
context.fillRect(0,0,386,320);
context.fillStyle="#517a9c";
context.fillRect(0,80,386,150);
context.fillStyle="#1d201d";
context.beginPath();
context.moveTo(33, 319);
context.lineTo(63, 26);
context.lineTo(97, 200);
context.lineTo(179, 319);
context.closePath();
context.fill();
context.fillStyle="#acae6e";
context.beginPath();
context.arc(355,52,35,0,6.3);
context.fill();
context.beginPath();
context.moveTo(0,0);
context.lineTo(300,150);
</script>
Attempt #2 (Sep 2018): 8000.55
For my second attempt, I switched to a rectangle-based approach and made some simple code optimizations to allow for 14+ rectangles.
This time around, I used Inkscape to arrange the shapes and experiment with the colors.
<canvas width="386" height="320" id="c">
<script>
var canvas = document.getElementById("c");
var context = canvas.getContext("2d");
function f(x,y,w,h,c)
{
context.fillStyle="#"+c;
context.fillRect(x,y,w,h);
}
//Top area
f(0,0,386,250,"607391");
f(0,0,215,48,"415084");
f(298,160,41,28,"9cab9b");
//Bottom area
f(0,250,386,70,"414f5d");
//Middle
f(200,200,188,56,"313f68");
f(133,223,67,27,"2c3955");
f(223,177,74,23,"869999");
f(322,131,64,29,"a9b4a4");
f(354,164,32,32,"26293a");
//Sun outer
f(318,17,67,70,"adaf7b");
//Star
f(107,148,45,44,"a1a9a1");
//Foreground mountain
f(45,210,88,168,"222420");
f(45,130,42,104,"222421");
f(58,20,18,112,"222421");
</script>
Attempt #3 (Mar 2022): 5999.90
For my third attempt, I minified the HTML code and added some size and color compression to the rectangles.
Instead of passing values like 240
to the function that draws the rectangles, I'm now passing values like 30
and having the function multiply them by 8
. That makes each function call several bytes smaller on average since the numbers generally have fewer digits.
I also restricted the colors to three-digit values like 6AF
rather than 66AAFF
or 65ABFE
. That restriction makes the colors slightly less accurate individually but saves up to three bytes per function call.
Those changes, along with minifying the HTML code, allowed me to nearly quadruple the number of rectangles!
The exact number of them that will fit into a kilobyte depends on their parameter values. For example, a width like 12
takes up more room than a width like 8
, and a mixed color value like "3c3"
requires quotes unlike an all-numeric color value such as 333
.
With the code below, I was able to paint 50 rectangles onto the canvas using 1021 bytes of barely-valid HTML and JavaScript.
<canvas width=386 height=320><script>function f(f,a,e,l,n){t.fillStyle='#'+n,t.fillRect(8*f,8*a,8*e,8*l)}v=document.querySelector`*>*>*`,t=v.getContext`2d`,f(0,22,39,13,678),f(16,27,33,10,346),f(26,10,24,14,899),f(5,10,8,17,456),f(22,33,27,7,334),f(0,0,50,14,347),f(0,7,51,8,568),f(0,11,45,11,579),f(40,17,9,4,"9A9"),f(0,29,21,8,234),f(2,0,4,4,678),f(39,2,13,10,"9a8"),f(42,22,7,4,224),f(0,35,8,5,343),f(3,1,2,2,995),f(9,0,2,2,667),f(14,0,4,3,678),f(18,2,2,2,776),f(27,2,4,3,787),f(0,14,5,2,"78A"),f(36,3,3,18,569),f(9,5,3,3,775),f(7,0,2,17,345),f(32,7,4,4,788),f(42,8,4,2,"BA3"),f(4,18,4,3,897),f(6,15,5,16,222),f(6,27,10,13,222),f(14,12,2,4,897),f(10,14,6,5,457),f(14,18,5,6,"9a9"),f(38,19,5,4,899),f(45,20,5,2,334),f(27,25,26,2,346),f(30,38,6,3,444),f(11,25,3,2,333),f(41,4,5,4,"BA6"),f(16,31,6,11,222),f(21,0,3,2,568),f(16,6,8,3,678),f(17,12,12,7,679),f(20,15,10,8,568),f(0,29,6,1,347),f(39,24,15,8,458),f(36,29,14,4,456),f(20,31,20,2,345),f(23,31,4,4,345),f(36,35,15,2,345),f(39,24,3,2,234),f(16,11,2,2,346)</script>
HTML tool (Mar 2022)
For the first two attempts, I had used simple trial and error in GIMP/Inkscape to generate some decent rectangle values, which was fine for the small number of rectangles being used. After starting on the third attempt, though, I realized that managing ~50 rectangles by hand would be a terrible idea, so I decided to write a separate HTML tool to automate the process.
The tool went through several iterations, some of which worked better than others, and what I ended up with was a tool that takes an initial array of rectangles and then repeats this loop:
- Randomly modify the dimensions, coordinates, and color values of several random rectangles in the initial array
- Paint the modified array's rectangles on a canvas and check its score
- If the modified array is an improvement, make it the new initial array
I generated some decent starting values by hand using Inkscape, let the tool run for a while in several browser windows, collected its best output, and then gradually updated the starting values and refreshed the page until I ended up with a score that was difficult to improve further.
Here's the final code for that tool. Due to CORS policy, the canvas becomes tainted if you try to run it locally, so you'll need to use a workaround like hosting it on a website. Also, you'll need to include the original Starry Night image in the same directory with the filename reference-image.png
.
The starting values included here are already heavily optimized, so you likely won't see any improvements being made unless you run the tool for a very long time or change the starting values.
<html>
<head>
<style>
html, body
{
margin: 0;
padding: 0;
}
.outer-container
{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
gap: 60px;
}
.inner-container
{
display: flex;
justify-content: center;
gap: 20px;
}
.canvas-container
{
display: flex;
flex-direction: column;
gap: 15px;
}
.textarea-container
{
width: 100%;
max-width: 1200px;
}
.textarea-container textarea
{
width: 100%;
height: 120px;
resize: none;
}
.canvas-text
{
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
font-weight: bold;
text-align: center;
}
</style>
</head>
<body>
<div class="outer-container">
<div class="inner-container">
<div class="canvas-container">
<canvas width="386" height="320" id="reference-image-canvas"></canvas>
<div class="canvas-text"><span>Original image</span></div>
</div>
<div class="canvas-container">
<canvas width=386 height=320 id=c></canvas>
<div class="canvas-text"><span>Comparison score:</span> <span id="comparison-score">TBD</span></div>
<div class="canvas-text"><span>Last 100 average:</span> <span id="average-score">TBD</span></div>
</div>
<div class="canvas-container">
<canvas width="386" height="320" id="best-score-canvas"></canvas>
<div class="canvas-text"><span>Best score:</span> <span id="best-score">TBD</span></div>
<div class="canvas-text"><span>Num rects:</span> <span id="num-rects">TBD</span></div>
</div>
</div>
<div class="textarea-container">
<textarea id="html-output-textarea" readonly rows="8"></textarea>
</div>
</div>
</body>
<script>
var best_score = Infinity;
var best_rects = [];
var last_100_scores = [];
function is_canvas_tainted(context)
{
try
{
var pixel = context.getImageData(0, 0, 1, 1);
return false;
}
catch (err)
{
return (err.code === 18);
}
}
//Prepend the given keys/values to the given index in the given associate array and return a new associate array
//Usage example: my_array = add_items_to_array_at_position(['one' => 'One', 'three' => 'Three'], 1, ['two' => 'Two'])
function add_items_to_array_at_position(array, index, new_items)
{
return [...array.slice(0, index), ...new_items, ...array.slice(index)];
}
//Return an int between min and max, inclusive
//Example: get_random_int_in_range(1, 5) === 5
function get_random_int_in_range(min, max)
{
min = Math.ceil(min);
max = Math.floor(max) + 1;
return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive
}
//Example: get_random_hex_code(3) === '08E'
function get_random_hex_code(num_characters)
{
let hex_code = '';
for (let i = 0; i < num_characters; i++)
{
hex_code = hex_code + '0123456789ABCDEF'.charAt(Math.floor(Math.random() * 16));
}
return hex_code;
}
//Example: paint_square_on_canvas(context, 25, 136, 180, 56, '35F')
function paint_square_on_canvas(context, x, y, width, height, color_code)
{
if (color_code === 'random')
{
color_code = get_random_3_digit_hex();
}
context.fillStyle = '#' + color_code;
context.fillRect(x * 8, y * 8, width * 8, height * 8);
}
//Compress a 0-to-255 red, green, and blue value down to a 3-digit hex code like '5fa'
//TODO: Is there a way to round the values better?
function convert_rgb_to_3_digit_hex(red, green, blue)
{
let red_hex = '0123456789ABCDEF'.charAt(Math.floor(red / 16));
let green_hex = '0123456789ABCDEF'.charAt(Math.floor(green / 16));
let blue_hex = '0123456789ABCDEF'.charAt(Math.floor(blue / 16));
return red_hex + green_hex + blue_hex;
}
function get_random_3_digit_hex()
{
let red_pixel = get_random_int_in_range(0, 255);
let green_pixel = get_random_int_in_range(0, 255);
let blue_pixel = get_random_int_in_range(0, 255);
let hex = convert_rgb_to_3_digit_hex(red_pixel, green_pixel, blue_pixel);
return hex;
}
function get_random_3_digit_hex_from_canvas(pixels)
{
let num_pixels = pixels.length;
let pixel_start_index = get_random_int_in_range(0, (pixels.length - 4) / 4) * 4;
let red_pixel = pixels[pixel_start_index + 0];
let green_pixel = pixels[pixel_start_index + 1];
let blue_pixel = pixels[pixel_start_index + 2];
let alpha_pixel = pixels[pixel_start_index + 3];
let hex = convert_rgb_to_3_digit_hex(red_pixel, green_pixel, blue_pixel);
return hex;
}
//Given a canvas context, top left coordinate, and rectangle width/height, return the average color found within the rectangle
function get_average_rectangle_color(context, rect_x, rect_y, rect_width, rect_height)
{
let canvas_width = context.canvas.clientWidth;
let canvas_height = context.canvas.clientHeight;
let ref_pixels = context.getImageData(rect_x, rect_y, rect_width, rect_height).data;
let reds = [];
let greens = [];
let blues = [];
for (let i = 0, n = ref_pixels.length; i < n; i += 4)
{
reds.push(ref_pixels[i+0]);
greens.push(ref_pixels[i+1]);
blues.push(ref_pixels[i+2]);
}
let average_red = reds.reduce((a, b) => a + b) / reds.length;
let average_green = greens.reduce((a, b) => a + b) / greens.length;
let average_blue = blues.reduce((a, b) => a + b) / blues.length;
return convert_rgb_to_3_digit_hex(average_red, average_green, average_blue);
}
function paint_random_squares_on_canvas(ref_context, test_context, num_squares, rects_used)
{
let canvas_width = test_context.canvas.clientWidth;
let canvas_height = test_context.canvas.clientHeight;
let ref_pixels = ref_context.getImageData(0, 0, canvas_width, canvas_height).data;
for (let i = 1; i < num_squares; i++)
{
let square_width = Math.round(get_random_int_in_range(4, canvas_width) / 8);
let square_height = Math.round(get_random_int_in_range(4, canvas_height) / 8); //squares
let square_x = Math.round(get_random_int_in_range(0, (canvas_width / 8) - square_width));
let square_y = Math.round(get_random_int_in_range(0, (canvas_height / 8) - square_height));
let color_code = get_average_rectangle_color(ref_context, square_x * 8, square_y * 8, square_width * 8, square_height * 8);
paint_square_on_canvas(test_context, square_x, square_y, square_width, square_height, color_code);
rects_used.push({
'square_width': square_width,
'square_height': square_height,
'square_x': square_x,
'square_y': square_y,
'color_code': color_code
});
}
}
function calculate_new_average(test_score)
{
last_100_scores.push(test_score);
if (last_100_scores.length === 100)
{
let average_score = Math.round(last_100_scores.reduce((a, b) => a + b) / last_100_scores.length);
let average_score_element = document.querySelector('#average-score');
average_score_element.innerHTML = average_score;
last_100_scores = [];
}
}
function check_for_new_best(test_pixels, test_score, rects_used)
{
calculate_new_average(test_score);
if (test_score >= best_score)
{
return false;
}
let best_canvas = document.querySelector('#best-score-canvas');
let best_context = best_canvas.getContext('2d');
let best_score_element = document.querySelector('#best-score');
best_score = test_score;
best_score_element.innerHTML = best_score.toFixed(2);
let num_rects_element = document.querySelector('#num-rects');
num_rects_element.innerHTML = rects_used.length;
best_context.putImageData(test_pixels, 0, 0);
return true;
}
function compare_images(ref_context, test_context, rects_used)
{
let ref_image_data = ref_context.getImageData(0, 0, ref_context.canvas.clientWidth, ref_context.canvas.clientHeight);
let ref_pixels = ref_image_data.data;
let test_image_data = test_context.getImageData(0, 0, test_context.canvas.clientWidth, test_context.canvas.clientHeight);
let test_pixels = test_image_data.data;
if (ref_pixels.length !== test_pixels.length)
{
alert('Error! The reference and test image must have the same number of pixels.');
return null;
}
let test_score = 0; //lower is better
for (let i = 0; i < ref_pixels.length; i += 4)
{
let ref_red = ref_pixels[i] / 255.0;
let ref_green = ref_pixels[i + 1] / 255.0;
let ref_blue = ref_pixels[i + 2] / 255.0;
let ref_alpha = ref_pixels[i + 3] / 255.0;
let test_red = test_pixels[i] / 255.0;
let test_green = test_pixels[i + 1] / 255.0;
let test_blue = test_pixels[i + 2] / 255.0;
let test_alpha = test_pixels[i + 3] / 255.0;
let pixel_diff = ((ref_red - test_red) ** 2) + ((ref_green - test_green) ** 2) + ((ref_blue - test_blue) ** 2);
test_score += pixel_diff;
}
let test_score_element = document.querySelector('#comparison-score');
test_score_element.innerHTML = test_score.toFixed(2);
let test_image_is_new_best = check_for_new_best(test_image_data, test_score, rects_used);
if (test_image_is_new_best === true)
{
let html_output_textarea = document.querySelector('#html-output-textarea');
best_rects = rects_used;
html_output_textarea.innerHTML = JSON.stringify(best_rects, null, 2);
}
}
function find_best_color_for_rect(ref_context, test_context)
{
}
function clamp_number(number, min, max)
{
}
//Return a random color code that's similar to the one provided
//max_changes controls the maximum number of characters in the string that will be changed
//max_distance controls the maximum distance that each character can be changed
//Example: get_adjacent_color_code('FC8', 2, 1) === 'EC9'
function get_adjacent_color_code(color_code, max_changes, max_distance)
{
let hex_chars = '0123456789ABCDEF';
let new_color_code = '';
[...color_code].forEach(function(temp_char, old_char_index)
{
let decimal_index = hex_chars.indexOf(temp_char);
let index_change = get_random_int_in_range(-max_distance, max_distance); //TODO: Exclude 0?
let new_char = hex_chars.charAt(Math.min(Math.max(0, decimal_index + index_change), hex_chars.length - 1));
new_color_code += new_char;
});
return new_color_code;
}
function try_more_test_squares(ref_context, test_context)
{
let rects_used = [];
let ref_image_data = ref_context.getImageData(0, 0, ref_context.canvas.clientWidth, ref_context.canvas.clientHeight);
let ref_pixels = ref_image_data.data;
ref_canvas_width = ref_context.canvas.clientWidth;
ref_canvas_height = ref_context.canvas.clientHeight;
test_canvas_width = test_context.canvas.clientWidth;
test_canvas_height = test_context.canvas.clientHeight;
test_context.clearRect(0, 0, test_canvas_width, test_canvas_height);
let num_squares = 45;
let bg_ref_square_width = Math.round(get_random_int_in_range(4, ref_canvas_width) / 8);
let bg_ref_square_height = Math.round(get_random_int_in_range(4, ref_canvas_height) / 8); //squares
let bg_ref_square_x = Math.round(get_random_int_in_range(0, (ref_canvas_width / 8) - bg_ref_square_width));
let bg_ref_square_y = Math.round(get_random_int_in_range(0, (ref_canvas_height / 8) - bg_ref_square_height));
let color_code = get_average_rectangle_color(ref_context, bg_ref_square_x * 8, bg_ref_square_y * 8, bg_ref_square_width * 8, bg_ref_square_height * 8);
let rects_to_use;
if (!best_rects || best_rects == false)
{
//5999.90 (50 rects)
rects_to_use = [{"square_x": 0, "square_y": 22, "square_width": 39, "square_height": 13, "color_code": "678"},{"square_x": 16, "square_y": 27, "square_width": 33, "square_height": 10, "color_code": "346"},{"square_x": 26, "square_y": 10, "square_width": 24, "square_height": 14, "color_code": "899"},{"square_x": 5, "square_y": 10, "square_width": 8, "square_height": 17, "color_code": "456"},{"square_x": 22, "square_y": 33, "square_width": 27, "square_height": 7, "color_code": "334"},{"square_x": 0, "square_y": 0, "square_width": 50, "square_height": 14, "color_code": "347"},{"square_x": -1, "square_y": 7, "square_width": 51, "square_height": 8, "color_code": "568"},{"square_x": 0, "square_y": 11, "square_width": 45, "square_height": 11, "color_code": "579"},{"square_x": 40, "square_y": 17, "square_width": 9, "square_height": 4, "color_code": "9A9"},{"square_x": 0, "square_y": 29, "square_width": 21, "square_height": 8, "color_code": "234"},{"square_x": 2, "square_y": 0, "square_width": 4, "square_height": 4, "color_code": "678"},{"square_x": 39, "square_y": 2, "square_width": 13, "square_height": 10, "color_code": "9a8"},{"square_x": 42, "square_y": 22, "square_width": 7, "square_height": 4, "color_code": "224"},{"square_x": -1, "square_y": 35, "square_width": 8, "square_height": 5, "color_code": "343"},{"square_x": 3, "square_y": 1, "square_width": 2, "square_height": 2, "color_code": "995"},{"square_x": 9, "square_y": 0, "square_width": 2, "square_height": 2, "color_code": "667"},{"square_x": 14, "square_y": 0, "square_width": 4, "square_height": 3, "color_code": "678"},{"square_x": 18, "square_y": 2, "square_width": 2, "square_height": 2, "color_code": "776"},{"square_x": 27, "square_y": 2, "square_width": 4, "square_height": 3, "color_code": "787"},{"square_x": -2, "square_y": 14, "square_width": 7, "square_height": 2, "color_code": "78A"},{"square_x": 36, "square_y": 3, "square_width": 3, "square_height": 18, "color_code": "569"},{"square_x": 9, "square_y": 5, "square_width": 3, "square_height": 3, "color_code": "775"},{"square_x": 7, "square_y": -2, "square_width": 2, "square_height": 17, "color_code": "345"},{"square_x": 32, "square_y": 7, "square_width": 4, "square_height": 4, "color_code": "788"},{"square_x": 42, "square_y": 8, "square_width": 4, "square_height": 2, "color_code": "BA3"},{"square_x": 4, "square_y": 18, "square_width": 4, "square_height": 3, "color_code": "897"},{"square_x": 6, "square_y": 15, "square_width": 5, "square_height": 16, "color_code": "222"},{"square_x": 6, "square_y": 27, "square_width": 10, "square_height": 13, "color_code": "222"},{"square_x": 14, "square_y": 12, "square_width": 2, "square_height": 4, "color_code": "897"},{"square_x": 10, "square_y": 14, "square_width": 6, "square_height": 5, "color_code": "457"},{"square_x": 14, "square_y": 18, "square_width": 5, "square_height": 6, "color_code": "9a9"},{"square_x": 38, "square_y": 19, "square_width": 5, "square_height": 4, "color_code": "899"},{"square_x": 45, "square_y": 20, "square_width": 5, "square_height": 2, "color_code": "334"},{"square_x": 27, "square_y": 25, "square_width": 26, "square_height": 2, "color_code": "346"},{"square_x": 30, "square_y": 38, "square_width": 6, "square_height": 3, "color_code": "444"},{"square_x": 11, "square_y": 25, "square_width": 3, "square_height": 2, "color_code": "333"},{"square_x": 41, "square_y": 4, "square_width": 5, "square_height": 4, "color_code": "BA6"},{"square_x": 16, "square_y": 31, "square_width": 6, "square_height": 11, "color_code": "222"},{"square_x": 24, "square_y": -1, "square_width": -3, "square_height": 3, "color_code": "568"},{"square_x": 16, "square_y": 6, "square_width": 8, "square_height": 3, "color_code": "678"},{"square_x": 17, "square_y": 12, "square_width": 12, "square_height": 7, "color_code": "679"},{"square_x": 30, "square_y": 15, "square_width": -10, "square_height": 8, "color_code": "568"},{"square_x": -2, "square_y": 29, "square_width": 8, "square_height": 1, "color_code": "347"},{"square_x": 39, "square_y": 24, "square_width": 15, "square_height": 8, "color_code": "458"},{"square_x": 36, "square_y": 29, "square_width": 14, "square_height": 4, "color_code": "456"},{"square_x": 20, "square_y": 33, "square_width": 20, "square_height": -2, "color_code": "345"},{"square_x": 23, "square_y": 31, "square_width": 4, "square_height": 4, "color_code": "345"},{"square_x": 36, "square_y": 35, "square_width": 15, "square_height": 2, "color_code": "345"},{"square_x": 39, "square_y": 24, "square_width": 3, "square_height": 2, "color_code": "234"},{"square_x": 16, "square_y": 11, "square_width": 2, "square_height": 2, "color_code": "346"}];
}
else
{
rects_to_use = structuredClone(best_rects);
let max_rects = 50;
//Chance to add a new rectangle
for (let i = 1; i < (get_random_int_in_range(0, max_rects - rects_to_use.length)); i++)
{
if (Math.random() < (.05))
{
let square_width = Math.round(get_random_int_in_range(1, test_canvas_width) / 8);
let square_height = Math.round(get_random_int_in_range(1, test_canvas_height) / 8); //squares
let square_x = Math.round(get_random_int_in_range(0, (test_canvas_width / 8) - square_width));
let square_y = Math.round(get_random_int_in_range(0, (test_canvas_height / 8) - square_height));
let color_code = get_random_hex_code(3);
let new_rect = {square_x: square_x, square_y: square_y, square_width: square_width, square_height: square_height, color_code: color_code};
rects_to_use = add_items_to_array_at_position(rects_to_use, get_random_int_in_range(0, rects_to_use.length - 1), [new_rect]);
}
}
//Chance to change each rectangle's color
for (let num_color_loops = 0; num_color_loops < 6; num_color_loops++)
{
if (Math.random() < .3)
{
rects_to_use[get_random_int_in_range(0, rects_to_use.length - 1)].color_code = get_random_3_digit_hex();
}
}
//Chance to change rectangle's position
let coord_change;
if (Math.random() < .2)
{
coord_change = ((Math.random() < .5) ? 1 : -1) * get_random_int_in_range(1, 6);
rects_to_use[get_random_int_in_range(0, rects_to_use.length - 1)].square_x += coord_change;
}
if (Math.random() < .2)
{
coord_change = ((Math.random() < .5) ? 1 : -1) * get_random_int_in_range(1, 6);
rects_to_use[get_random_int_in_range(0, rects_to_use.length - 1)].square_y += coord_change;
}
if (Math.random() < .2)
{
coord_change = ((Math.random() < .5) ? 1 : -1) * get_random_int_in_range(1, 6);
rects_to_use[get_random_int_in_range(0, rects_to_use.length - 1)].square_width += coord_change;
}
if (Math.random() < .2)
{
coord_change = ((Math.random() < .5) ? 1 : -1) * get_random_int_in_range(1, 6);
rects_to_use[get_random_int_in_range(0, rects_to_use.length - 1)].square_height += coord_change;
}
if (Math.random() < .2)
{
coord_change = ((Math.random() < .5) ? 1 : -1) * get_random_int_in_range(1, 6);
rects_to_use[get_random_int_in_range(0, rects_to_use.length - 1)].square_height += coord_change;
}
}
rects_to_use.forEach(function(rect)
{
paint_square_on_canvas(test_context, rect.square_x, rect.square_y, rect.square_width, rect.square_height, rect.color_code);
rects_used.push(rect);
});
compare_images(ref_context, test_context, rects_used);
}
document.addEventListener("DOMContentLoaded", function()
{
let ref_canvas = document.querySelector('#reference-image-canvas');
let ref_context = ref_canvas.getContext('2d');
let test_canvas = document.querySelector('#c');
let test_context = test_canvas.getContext('2d');
let ref_image = new Image();
ref_image.onload = function()
{
ref_context.drawImage(ref_image, 0, 0);
setInterval(function(){try_more_test_squares(ref_context, test_context)}, 1);
};
ref_image.src = 'reference-image.png';
});
</script>
</html>
Example of my workflow using the tool:
Future plans
My approach so far has been limited to rectangles. Based on how curvy the original Starry Night painting is, I could probably achieve a higher score by using circles or polygons.
I also realized too late that I overcompressed the coordinates and dimensions in attempt #3 and could have allowed the rectangles to have slightly more precision without adding any additional bytes.
After finishing attempt #3, I tested out several canvas filters and was able to immediately improve its final score by several hundred points using blur, drop-shadow, and opacity. However, I decided to exclude those improvements for now until I can properly incorporate them into the tool.
pip unistall PIL
, thenpip install pillow
) and change the first line tofrom PIL import Image
. \$\endgroup\$