Skip to main content

Using a Genetic Algorithm to Draw Images

Photo by Sangharsh Lohakare on Unsplash


Have you ever wondered how an image can be recreated by evolutionary principles? Genetic algorithms (GA) provide a fascinating way to approach this challenge. They simulate the process of natural selection by evolving a population of potential solutions over successive generations.

In this project, I revisited an open-source contribution I made years ago, where I attempted to replicate user-uploaded images using a genetic algorithm. Although the original pull request was not accepted due to some unnoticed broken code, my fascination with the concept never faded. Inspired to refine it, I rebuilt and enhanced the implementation. Here’s the full story of how it works.

What is a Genetic Algorithm?

A genetic algorithm is an optimization technique inspired by natural evolution. It works by:

  1. Initialization: Generating an initial population of potential solutions.
  2. Evaluate Fitness: Measure how closely each solution resembles the target.
  3. Selection: Choose the best candidates to pass their traits to the next generation.
  4. Crossover: Combining pairs of solutions to produce offspring.
  5. Mutation: Randomly altering some solutions to introduce variation.
  6. Iteration: Repeating the process over multiple generations until a solution emerges.

In this project, the algorithm attempts to match an uploaded image by evolving randomly generated shapes on a canvas.

Project Overview

In this project, users can upload an image, and the algorithm will attempt to replicate it by evolving random patterns. Each generation produces better approximations of the original image by evaluating and evolving previous solutions.

We’ll start with a simple HTML structure that includes an upload button, two canvases (for the target and evolving images), and a button to start the algorithm.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Genetic Algorithm - Image Matching</title>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}

.container {
display: flex;
}

.container div {
margin: 20px;
}
</style>
</head>
<body>
<h1>Upload an Image for Matching</h1>
<input type="file" id="imageUpload" accept="image/*" />
<br/><br/>

<div class="container">
<div>
<h2>Uploaded Image:</h2>
<canvas id="uploadedCanvas" width="300" height="300"></canvas>
</div>

<div>
<h2>Genetic Algorithm Output:</h2>
<canvas id="outputCanvas" width="300" height="300"></canvas>

<p>Current match: <span id="matchPercentage"></span>%</p>
<p>Generation: <span id="generationCount"></span></p>
<br/>

<button id="startGeneticAlgorithm">Start Genetic Algorithm</button>
</div>
</div>

<script>
const uploadedCanvas = document.getElementById("uploadedCanvas");
const outputCanvas = document.getElementById("outputCanvas");
const ctxUploaded = uploadedCanvas.getContext("2d");
const ctxOutput = outputCanvas.getContext("2d");
const matchSpan = document.getElementById("matchPercentage");
const genSpan = document.getElementById("generationCount");

let targetData = null;

const imageUpload = document.getElementById("imageUpload");
imageUpload.addEventListener("change", handleImageUpload);

function handleImageUpload(event) {
const file = event.target.files[0];

if (file) {
const targetImage = new Image();
const reader = new FileReader();

reader.onload = function(e) {
targetImage.src = e.target.result;
};

targetImage.onload = function() {
ctxUploaded.clearRect(0, 0, uploadedCanvas.width, uploadedCanvas.height);
ctxUploaded.drawImage(targetImage, 0, 0, uploadedCanvas.width, uploadedCanvas.height);

targetData = getImageData(targetImage);
};

reader.readAsDataURL(file);
}
}

function getImageData(image) {
ctxOutput.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
ctxOutput.drawImage(image, 0, 0, outputCanvas.width, outputCanvas.height);
return ctxOutput.getImageData(0, 0, outputCanvas.width, outputCanvas.height);
}

const startButton = document.getElementById("startGeneticAlgorithm");
startButton.addEventListener("click", startGeneticAlgorithm);

function startGeneticAlgorithm() {
if (!targetData) {
alert("Please upload an image first.");
return;
}

const populationSize = 50;
let population = new ImageSamplePopulation(targetData, populationSize);
population.populate();
}
</script>
</body>
</html>

The following code defines two classes, ImageSample and ImageSamplePopulation, which handle the creation, mutation, and evolution of images.

class ImageSample {
constructor(config) {
this.mutationRate = config.mutationRate;
this.shapeRate = config.shapeRate;
this.target = config.target
this.width = config.width;
this.height = config.height;

if (config.image) {
this.image = config.image;
} else {
this.randomizeImage();
}
}

randomizeImage() {
let baseImageLayer = new ImageData(this.width, this.height);

if (this.image) {
baseImageLayer = this.image;
}

ctxOutput.putImageData(baseImageLayer, 0, 0);

const color = this.getRandomColor();

if (Math.random() < this.shapeRate) {
const x = this.getRandomInt(0, this.width);
const y = this.getRandomInt(0, this.height);
const radius = this.getRandomFloat(1, Math.min(this.width / 2, this.height / 2));

this.drawCircle(ctxOutput, color, x, y, radius);
} else {
let coordinates = [
this.getRandomInt(0, this.width),
this.getRandomInt(0, this.height),
this.getRandomInt(0, this.width),
this.getRandomInt(0, this.height),
this.getRandomInt(0, this.width),
this.getRandomInt(0, this.height),
];

this.drawShape(ctxOutput, color, coordinates);
}

this.image = ctxOutput.getImageData(0, 0, this.width, this.height);
this.fitness = this.computeFitness(this.target);
}

getRandomColor() {
var letters = "0123456789ABCDEF";
var color = "#";

for (var i = 0; i < 8; i++) {
color += letters[Math.floor(Math.random() * 16)];
}

return color;
}

getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}

getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}

drawLine(ctx, color, x1, y1, x2, y2, width) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.stroke();
}

drawCircle(ctx, color, x, y, radius) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}

drawEllipse(ctx, color, x, y, radiusX, radiusY) {
ctx.beginPath();
ctx.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}

drawShape(ctx, color, coords) {
ctx.beginPath();
ctx.moveTo(coords[0], coords[1]);

for (let i = 2; i < coords.length; i += 2) {
ctx.lineTo(coords[i], coords[i + 1]);
}

ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}

mutate() {
const mutationResistance = 0.5;

if (this.mutationRate < mutationResistance) return;

this.randomizeImage();
}

crossover(partner) {
let crossoverRate = Math.floor(Math.random() * partner.image.data.length);
crossoverRate = crossoverRate > 0 ? crossoverRate : 0.5;

const parentsMutationRate = (this.mutationRate + partner.mutationRate) / 2;
const parentsShapeRate = (this.shapeRate + partner.shapeRate) / 2;
let offspringImage = this.image;

const parentImageData1 = this.image.data;
const parentImageData2 = partner.image.data;

for (var i = 0; i < parentImageData1.length; i += 4) {
offspringImage.data[i] = parentImageData1[i] * crossoverRate + parentImageData2[i] * (1 - crossoverRate); // red
offspringImage.data[i + 1] = parentImageData1[i + 1] * crossoverRate + parentImageData2[i + 1] * (1 - crossoverRate); // green
offspringImage.data[i + 2] = parentImageData1[i + 2] * crossoverRate + parentImageData2[i + 2] * (1 - crossoverRate); // blue
}

const offspring = {
mutationRate: (Math.random() + parentsMutationRate) / 2,
shapeRate: (Math.random() + parentsShapeRate) / 2,
target: this.target,
width: this.width,
height: this.height,
image: offspringImage
};

return new ImageSample(offspring);
}

computeFitness() {
this.fitness = 0;

for (let i = 0; i < this.target.data.length; i += 4) {
this.fitness += Math.sqrt(
Math.pow(this.target.data[i] - this.image.data[i], 2)
+ Math.pow(this.target.data[i + 1] - this.image.data[i + 1], 2)
+ Math.pow(this.target.data[i + 2] - this.image.data[i + 2], 2)
);
}
}

hasAchievedTarget() {
return this.image == this.target
}
}

class ImageSamplePopulation {
constructor(target, populationSize) {
this.imageSamples = [];
this.oldImageSamples = [];

const { width, height } = target;
this.target = target;
this.width = width;
this.height = height;

this.generationNo = 0;

while (populationSize--) {
this.imageSamples.push(new ImageSample({
mutationRate: Math.random(),
shapeRate: Math.random(),
target: this.target,
width: this.width,
height: this.height
}));
}

this.bestImageSample = this.imageSamples[0];
this.elitismCount = Math.ceil(this.imageSamples.length * 0.5);
}

sortFitness(a, b) {
return a.fitness - b.fitness;
}

showGeneration() {
const maxDiff = this.width * this.height * 3 * 255;

genSpan.textContent = this.generationNo;
matchSpan.textContent = (100 * (1 - this.bestImageSample.fitness / maxDiff)).toFixed(2);

ctxOutput.putImageData(this.bestImageSample.image, 0, 0);
}

populate() {
this.imageSamples.sort(this.sortFitness);
this.showGeneration();
this.oldImageSamples = this.imageSamples;

let isPerfectGeneration = false;

const offspringCount = this.imageSamples.length - this.elitismCount;
let offsprings = [];

for (let i = 0; i < offspringCount; i++) {
const bestParent = this.imageSamples[0];
const randomParent = this.imageSamples[1];

const offspring = bestParent.crossover(randomParent);

offsprings.push(offspring);
}

this.imageSamples.splice(this.imageSamples.length - offsprings.length, offsprings.length, ...offsprings);

this.imageSamples.forEach(sample => {
sample.mutate();
sample.computeFitness();

if (sample.hasAchievedTarget()) {
isPerfectGeneration = true;
}
})

if (isPerfectGeneration) {
this.imageSamples.sort(this.sortFitness);
this.showGeneration();
} else {
this.generationNo++;

const self = this;

setTimeout(function() {
const oldImageSamplesTargetCount = Math.ceil(self.elitismCount / 2);
const newImageSamplesTargetCount = Math.ceil(self.elitismCount / 2);

const bestImageSamples = [
...self.findBestImageSamples(self.oldImageSamples, oldImageSamplesTargetCount),
...self.findBestImageSamples(self.imageSamples, newImageSamplesTargetCount)
];

bestImageSamples.sort(self.sortFitness);

self.bestImageSample = bestImageSamples[0];

for (let i = bestImageSamples.length; i < self.imageSamples.length; i++) {
self.imageSamples[i].image = bestImageSamples[i % bestImageSamples.length].image;
self.imageSamples[i].fitness = bestImageSamples[i % bestImageSamples.length].fitness;
}

self.populate();
}, 0);
}
}

findBestImageSamples(arr, len) {
if (arr.length < len) len = arr.length - 1;
return [...arr].sort((a, b) => a.fitness - b.fitness).slice(0, len)
}
}

Represents an individual image sample in the population, including methods for mutation, crossover, and fitness calculation. Each ImageSample is an attempt to approximate the target image.

Constructor: constructor(config)

Initializes the ImageSample instance with given configuration values.

Parameters:

  • config (Object): Configuration object with the following properties:
  • mutationRate (number): Probability of mutation during evolution.
  • shapeRate (number): Probability of adding a shape (circle or polygon).
  • target (ImageData): Target image data to approximate.
  • width (number): Width of the image.
  • height (number): Height of the image.
  • image (ImageData, optional): Initial image data (optional).

Methods:

randomizeImage()

  • Randomizes the ImageSample by drawing either a circle or polygon with random properties.

getRandomColor()

  • Generates a random hexadecimal color string.

getRandomInt(min, max)

  • Returns a random integer between min (inclusive) and max (exclusive).

getRandomFloat(min, max)

  • Returns a random floating-point number between min and max.

drawLine(ctx, color, x1, y1, x2, y2, width)

  • Draws a line on the canvas.

drawCircle(ctx, color, x, y, radius)

  • Draws a filled circle on the canvas.

drawEllipse(ctx, color, x, y, radiusX, radiusY)

  • Draws a filled ellipse on the canvas.

drawShape(ctx, color, coords)

  • Draws a polygon with the given coordinates.

mutate()

  • Randomizes the image based on the mutation rate, introducing variation.

crossover(partner)

  • Combines the image data of two ImageSample instances to create an offspring.

computeFitness()

  • Calculates the fitness of the current image by comparing it to the target image.

hasAchievedTarget()

  • Checks if the current image matches the target image exactly.

Represents a population of ImageSample instances, controlling the evolution process over multiple generations using genetic algorithm techniques like elitism and crossover.

Constructor: constructor(target, populationSize)

Initializes the ImageSamplePopulation with a target image and a population size.

Parameters:

  • target (ImageData): The target image data to evolve toward.
  • populationSize (number): Number of ImageSample instances in the population.

Methods:

sortFitness(a, b)

  • Sorts two ImageSample instances by fitness.

showGeneration()

  • Displays the best ImageSample in the current generation and updates the fitness percentage.

populate()

  • Advances the population by one generation, performing crossover, mutation, and elitism.

findBestImageSamples(arr, len)

  • Finds the len best ImageSample instances from an array based on fitness.

Complete Project Code

Below is the complete code for the project, ready for you to explore and experiment with:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Genetic Algorithm - Image Matching</title>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}

.container {
display: flex;
}

.container div {
margin: 20px;
}
</style>
</head>
<body>
<h1>Upload an Image for Matching</h1>
<input type="file" id="imageUpload" accept="image/*" />
<br/><br/>

<div class="container">
<div>
<h2>Uploaded Image:</h2>
<canvas id="uploadedCanvas" width="300" height="300"></canvas>
</div>

<div>
<h2>Genetic Algorithm Output:</h2>
<canvas id="outputCanvas" width="300" height="300"></canvas>

<p>Current match: <span id="matchPercentage"></span>%</p>
<p>Generation: <span id="generationCount"></span></p>
<br/>

<button id="startGeneticAlgorithm">Start Genetic Algorithm</button>
</div>
</div>

<script>
class ImageSample {
constructor(config) {
this.mutationRate = config.mutationRate;
this.shapeRate = config.shapeRate;
this.target = config.target
this.width = config.width;
this.height = config.height;

if (config.image) {
this.image = config.image;
} else {
this.randomizeImage();
}
}

randomizeImage() {
let baseImageLayer = new ImageData(this.width, this.height);

if (this.image) {
baseImageLayer = this.image;
}

ctxOutput.putImageData(baseImageLayer, 0, 0);

const color = this.getRandomColor();

if (Math.random() < this.shapeRate) {
const x = this.getRandomInt(0, this.width);
const y = this.getRandomInt(0, this.height);
const radius = this.getRandomFloat(1, Math.min(this.width / 2, this.height / 2));

this.drawCircle(ctxOutput, color, x, y, radius);
} else {
let coordinates = [
this.getRandomInt(0, this.width),
this.getRandomInt(0, this.height),
this.getRandomInt(0, this.width),
this.getRandomInt(0, this.height),
this.getRandomInt(0, this.width),
this.getRandomInt(0, this.height),
];

this.drawShape(ctxOutput, color, coordinates);
}

this.image = ctxOutput.getImageData(0, 0, this.width, this.height);
this.fitness = this.computeFitness(this.target);
}

getRandomColor() {
var letters = "0123456789ABCDEF";
var color = "#";

for (var i = 0; i < 8; i++) {
color += letters[Math.floor(Math.random() * 16)];
}

return color;
}

getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}

getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}

drawLine(ctx, color, x1, y1, x2, y2, width) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.stroke();
}

drawCircle(ctx, color, x, y, radius) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}

drawEllipse(ctx, color, x, y, radiusX, radiusY) {
ctx.beginPath();
ctx.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}

drawShape(ctx, color, coords) {
ctx.beginPath();
ctx.moveTo(coords[0], coords[1]);

for (let i = 2; i < coords.length; i += 2) {
ctx.lineTo(coords[i], coords[i + 1]);
}

ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}

mutate() {
const mutationResistance = 0.5;

if (this.mutationRate < mutationResistance) return;

this.randomizeImage();
}

crossover(partner) {
let crossoverRate = Math.floor(Math.random() * partner.image.data.length);
crossoverRate = crossoverRate > 0 ? crossoverRate : 0.5;

const parentsMutationRate = (this.mutationRate + partner.mutationRate) / 2;
const parentsShapeRate = (this.shapeRate + partner.shapeRate) / 2;
let offspringImage = this.image;

const parentImageData1 = this.image.data;
const parentImageData2 = partner.image.data;

for (var i = 0; i < parentImageData1.length; i += 4) {
offspringImage.data[i] = parentImageData1[i] * crossoverRate + parentImageData2[i] * (1 - crossoverRate); // red
offspringImage.data[i + 1] = parentImageData1[i + 1] * crossoverRate + parentImageData2[i + 1] * (1 - crossoverRate); // green
offspringImage.data[i + 2] = parentImageData1[i + 2] * crossoverRate + parentImageData2[i + 2] * (1 - crossoverRate); // blue
}

const offspring = {
mutationRate: (Math.random() + parentsMutationRate) / 2,
shapeRate: (Math.random() + parentsShapeRate) / 2,
target: this.target,
width: this.width,
height: this.height,
image: offspringImage
};

return new ImageSample(offspring);
}

computeFitness() {
this.fitness = 0;

for (let i = 0; i < this.target.data.length; i += 4) {
this.fitness += Math.sqrt(
Math.pow(this.target.data[i] - this.image.data[i], 2)
+ Math.pow(this.target.data[i + 1] - this.image.data[i + 1], 2)
+ Math.pow(this.target.data[i + 2] - this.image.data[i + 2], 2)
);
}
}

hasAchievedTarget() {
return this.image == this.target
}
}

class ImageSamplePopulation {
constructor(target, populationSize) {
this.imageSamples = [];
this.oldImageSamples = [];

const { width, height } = target;
this.target = target;
this.width = width;
this.height = height;

this.generationNo = 0;

while (populationSize--) {
this.imageSamples.push(new ImageSample({
mutationRate: Math.random(),
shapeRate: Math.random(),
target: this.target,
width: this.width,
height: this.height
}));
}

this.bestImageSample = this.imageSamples[0];
this.elitismCount = Math.ceil(this.imageSamples.length * 0.5);
}

sortFitness(a, b) {
return a.fitness - b.fitness;
}

showGeneration() {
const maxDiff = this.width * this.height * 3 * 255;

genSpan.textContent = this.generationNo;
matchSpan.textContent = (100 * (1 - this.bestImageSample.fitness / maxDiff)).toFixed(2);

ctxOutput.putImageData(this.bestImageSample.image, 0, 0);
}

populate() {
this.imageSamples.sort(this.sortFitness);
this.showGeneration();
this.oldImageSamples = this.imageSamples;

let isPerfectGeneration = false;

const offspringCount = this.imageSamples.length - this.elitismCount;
let offsprings = [];

for (let i = 0; i < offspringCount; i++) {
const bestParent = this.imageSamples[0];
const randomParent = this.imageSamples[1];

const offspring = bestParent.crossover(randomParent);

offsprings.push(offspring);
}

this.imageSamples.splice(this.imageSamples.length - offsprings.length, offsprings.length, ...offsprings);

this.imageSamples.forEach(sample => {
sample.mutate();
sample.computeFitness();

if (sample.hasAchievedTarget()) {
isPerfectGeneration = true;
}
})

if (isPerfectGeneration) {
this.imageSamples.sort(this.sortFitness);
this.showGeneration();
} else {
this.generationNo++;

const self = this;

setTimeout(function() {
const oldImageSamplesTargetCount = Math.ceil(self.elitismCount / 2);
const newImageSamplesTargetCount = Math.ceil(self.elitismCount / 2);

const bestImageSamples = [
...self.findBestImageSamples(self.oldImageSamples, oldImageSamplesTargetCount),
...self.findBestImageSamples(self.imageSamples, newImageSamplesTargetCount)
];

bestImageSamples.sort(self.sortFitness);

self.bestImageSample = bestImageSamples[0];

for (let i = bestImageSamples.length; i < self.imageSamples.length; i++) {
self.imageSamples[i].image = bestImageSamples[i % bestImageSamples.length].image;
self.imageSamples[i].fitness = bestImageSamples[i % bestImageSamples.length].fitness;
}

self.populate();
}, 0);
}
}

findBestImageSamples(arr, len) {
if (arr.length < len) len = arr.length - 1;
return [...arr].sort((a, b) => a.fitness - b.fitness).slice(0, len)
}
}

const uploadedCanvas = document.getElementById("uploadedCanvas");
const outputCanvas = document.getElementById("outputCanvas");
const ctxUploaded = uploadedCanvas.getContext("2d");
const ctxOutput = outputCanvas.getContext("2d");
const matchSpan = document.getElementById("matchPercentage");
const genSpan = document.getElementById("generationCount");

let targetData = null;

const imageUpload = document.getElementById("imageUpload");
imageUpload.addEventListener("change", handleImageUpload);

function handleImageUpload(event) {
const file = event.target.files[0];

if (file) {
const targetImage = new Image();
const reader = new FileReader();

reader.onload = function(e) {
targetImage.src = e.target.result;
};

targetImage.onload = function() {
ctxUploaded.clearRect(0, 0, uploadedCanvas.width, uploadedCanvas.height);
ctxUploaded.drawImage(targetImage, 0, 0, uploadedCanvas.width, uploadedCanvas.height);

targetData = getImageData(targetImage);
};

reader.readAsDataURL(file);
}
}

function getImageData(image) {
ctxOutput.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
ctxOutput.drawImage(image, 0, 0, outputCanvas.width, outputCanvas.height);
return ctxOutput.getImageData(0, 0, outputCanvas.width, outputCanvas.height);
}

const startButton = document.getElementById("startGeneticAlgorithm");
startButton.addEventListener("click", startGeneticAlgorithm);

function startGeneticAlgorithm() {
if (!targetData) {
alert("Please upload an image first.");
return;
}

const populationSize = 50;
let population = new ImageSamplePopulation(targetData, populationSize);
population.populate();
}
</script>
</body>
</html>

Recommendation on How to Run the Code

It’s recommended to serve the HTML file using Express.js. Follow the step-by-step guide below to set up an Express.js environment:

Node.js is a JavaScript runtime that allows you to run JavaScript code on the server side. It comes with npm (Node Package Manager), which helps you install and manage packages.

Installation Steps:

  • Download and install Node.js from the official website.
  • To verify the installation, open your terminal and run:
node -v
npm -v

This should display the installed versions of Node.js and npm.

Open your terminal and create a new directory for the project. Navigate into that directory:

mkdir ga-image-project
cd ga-image-project

Alternatively, you can create a new directory using the file explorer and then open the terminal in that location.

Run the following command to create a package.json file, which will manage your project dependencies:

npm init -y

The -y flag automatically answers "yes" to all prompts, creating a default package.json file.

To install the dependencies, run:

npm install express

Setup a simple Express.js endpoint to serve the HTML file using the following code:

// server.js
const express = require('express');
const path = require('path');

const app = express();

// Serve static files (images, CSS, etc.) from the "public" directory
app.use(express.static(path.join(__dirname, 'public')));

// Handle the route for the homepage, serving the HTML
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});

// Start the server on port 3000
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});

You may start the server using the following command:

node server.js

Once, running you should be able to access the page on http://localhost:3000.

Home Page for the Genetic Algorithm Image Matching Project

Upload your image and watch the algorithm generate an image.

Sample Genetic Algorithm Output

Conclusion

In this exploration of genetic algorithms through image evolution, we’ve seen how simple principles like mutation, crossover, and selection can drive a population of images toward an ideal target. However, this implementation is far from perfect. While it demonstrates the core concepts effectively, it takes time to converge, and the resulting images often come close to the target but rarely achieve exact replication.

This leaves room for improvement — and that’s where the real challenge lies. I encourage you, the reader, to dive deeper into this code, experiment, and find ways to enhance its efficiency and accuracy. Can you make it converge faster? Can you fine-tune the mutation and crossover mechanisms for better results? The beauty of algorithms like this lies in their flexibility, and your innovations could push it to new heights.

Happy coding, and may your evolutionary journey be as rewarding as the process itself!

Comments

Popular posts from this blog

Understanding Number Systems: Decimal, Binary, and Hexadecimal

In everyday life, we use numbers all the time, whether for counting, telling time, or handling money. The number system we’re most familiar with is the   decimal system , but computers use other systems, such as   binary   and   hexadecimal . Let’s break down these number systems to understand how they work. What is a Number System? A number system is a way of representing numbers using a set of symbols and rules. The most common number systems are: Decimal (Base 10) Binary (Base 2) Hexadecimal (Base 16) Each system has a different “base” that tells us how many unique digits (symbols) are used to represent numbers. Decimal Number System (Base 10) This is the system we use daily. It has  10 digits , ranging from  0 to 9 . Example: The number  529  in decimal means: 5 × 1⁰² + 2 × 1⁰¹ + 9 × 1⁰⁰ =  500 + 20 + 9 = 529 Each position represents a power of 10, starting from the rightmost digit. Why Base 10? Decimal is base 10 because it has 10 digits...

How to Monetize Your API as an Individual Developer While Hosting on Your Own Server?

In the API economy, cloud services like AWS, Google Cloud, and Azure offer many conveniences, such as scaling and infrastructure management. However, some developers prefer more control and autonomy, opting to host their APIs on personal servers. Whether for cost efficiency, data privacy, or customization, hosting your own API comes with both advantages and challenges. But, even without cloud platforms, there are effective ways to monetize your API. This guide will explore how individual developers can successfully monetize their APIs while hosting them on their own servers. Why Host Your API on Your Own Server? Hosting your own API gives you full control over the infrastructure and potentially lower long-term costs. Here’s why some developers choose this approach: Cost Control : Instead of paying ongoing cloud fees, you may opt for a one-time or lower-cost hosting solution that fits your budget and resource needs. Data Ownership : You have full control over data, which is critical if ...

API Testing with Jest and Supertest: A Step-by-Step Guide

API testing is essential to ensure your endpoints behave as expected across all scenarios. In this guide, we’ll explore how to use Jest and Supertest to test a sample API with various response types, including success, authentication errors, and validation errors. By the end, you’ll understand how to apply these tools to check for different response structures and status codes. 0. Prerequisites: Setting Up Your Environment Before diving into API testing, it’s important to ensure that your development environment is properly set up. Here’s what you need to do: Step 1: Install Node.js and npm Node.js  is a JavaScript runtime that allows you to run JavaScript code on the server side. It comes with  npm  (Node Package Manager), which helps you install and manage packages. Installation Steps: Download and install Node.js from the  official website . To verify the installation, open your terminal and run: node -v npm -v This should display the installed versions of Node.js...

The Weight of Responsibility: A Developer’s Journey to Balance Passion and Reality

For the past several years, Eddie has been on a steady climb in his career as a developer, but recently, he found himself at a crossroads — caught between the weight of his responsibilities and the desire to pursue his true passions. His journey began with a three-month internship as a web developer, which led to nearly four years in an application developer role. After that, he spent almost a year as a systems associate, managing tasks across systems analysis, quality assurance, and business analysis. Eventually, he returned to full-time software development for another two years before transitioning into more complex roles. For over a year, he worked as a multi-role software developer and database administrator before stepping into his current position as a senior software developer, database administrator, and cloud administrator — occasionally handling security tasks as well. Now, with over 8 years of professional experience, he also leads a small team of developers, which has been...

Avoiding Confusion in API Design: The Importance of Clear Responses

In today’s fast-paced software development landscape, APIs play a crucial role in connecting services and enabling functionality. However, poor design choices can lead to confusion and inefficiency for both developers and users. One such choice is the omission of a response body for successful requests, a practice I recently encountered in an enterprise API designed for bill payments. The Case of the No-Response API The API in question serves two main endpoints: one for inquiring about account validity and another for confirming payment. When successful, the API returned a  200 OK  status but no response body. This design choice led to significant confusion during our integration process. Even the internal team who developed the said API struggled to justify this approach, revealing a lack of clarity around the rationale behind it. Pros of This Design Choice While the intention behind this design may have been to streamline responses, several potential benefits can be identifi...