Paint by numbers

I’d like to create a “paint by numbers” effect so similar color ranges (with tolerance) are rounded and numerized so a child can paint those islands with a specific color

. how could I achieve that in ImageJ or Fiji?

1 Like

Hi @dentopolis,

This was an interesting problem. Although it may not do everything you want, I put together this macro:

title = getTitle();

run("Duplicate...", "title=1");
run("Gaussian Blur...", "sigma=2"); //for smaller or initially simple images, consider commenting-out this blur
run("Gaussian Blur...", "sigma=2");
run("Enhance Local Contrast (CLAHE)", "blocksize=49 histogram=10 maximum=2 mask=*None* fast_(less_accurate)");

run("Kuwahara Filter", "sampling=5 filter");

run("8-bit Color", "number=30");
run("RGB Color");

setBatchMode("hide");

colours = ""

for (y = 0; y < getHeight(); y++) {
	for (x = 0; x < getWidth(); x++) {
		v = getPixel(x, y);
		if (colours.contains(v)) {
			continue;
		} else {
			colours = colours + " " + v;
		}
	}
}

colours = String.trim(colours)
coloursArray = split(colours, " ");
print("Number of colours = "+coloursArray.length);

run("Duplicate...", "title=2");
run("8-bit");
setForegroundColor(0, 0, 0);
fill();

for (i = 0; i < coloursArray.length; i++) {
	for (y = 0; y < getHeight(); y++) {
		for (x = 0; x < getWidth(); x++) {
			selectWindow("1");
			v = getPixel(x, y);
	        selectWindow("2");
			if (coloursArray[i] == v) {
				setPixel(x, y, 255);
			} else {
				setPixel(x, y, 0);
			}
		}
	}
updateDisplay();
	run("Remove Outliers...", "radius=1 threshold=50 which=Bright"); //optionally remove small areas of colour
run("Create Selection");
if (selectionType() != -1 ) {
	setBatchMode("exit and display");
	roiManager("add");
	selectWindow("2");
	setBatchMode("hide");
	run("Select None");
	fill();
	selectWindow("1");
	setBatchMode("hide");
	}
}

close("2");
selectWindow("1");
rename(title+"_simplified");
roiManager("deselect");

run("Duplicate...", "title=colourless");
run("8-bit");
setForegroundColor(255, 255, 255);
//fill();

for (j = 0; j < roiManager("count"); j++) {
	roiManager("select", j);
	Roi.setStrokeColor(80, 80, 80);
	roiManager("update");
		/* Randomly assign colours to each region... its a little psychedelic
		r = random*230;
		g = random*230;
		b = random*230;
		setForegroundColor(r, g, b);
		fill();
		*/
				/* This block to add the orginal colours back in... image also needs to be RGB
				color = coloursArray[j];
				setForegroundColor(color);
				fill();
				*/
}
setForegroundColor(0, 0, 0);
setBackgroundColor(255, 255, 255);
roiManager("deselect");
run("Select None");
run("RGB Color");
setBatchMode("exit and display");
//roiManager("Show All without labels");
roiManager("Show None");

newImage("Palette", "RGB white", (50*coloursArray.length), 50, 1);
for (k = 0; k < coloursArray.length; k++) {
	run("Specify...", "width=50 height=50 x="+(k*50)+" y=0");
	col = coloursArray[k];
	setForegroundColor(col);
	fill();
	setForegroundColor(0, 0, 0);
	run("Draw", "slice");
}
run("Select None");
setForegroundColor(255, 255, 255);

Which simplifies and decolours an input RGB image, whilst also producing a colour palette of the simplified version. For example:

I didn’t try to optimise the macro too greatly, so I wouldn’t try to run it on very large original images.
It was also a design choice to output a greyscale image at the end, rather than only outlines, as it ended up looking a lot better with so many ROIs in play.
The macro also populates the ‘ROI manager’ with all the colour regions.

Even if this isn’t everything you want, hopefully the script will get you closer to your goal.

Kind regards.

EDIT: I forgot to mention that there is one very useful line in the macro (line 10):

run("8-bit Color", "number=30");

which you can easily modify to set the number of final colours. Just change the number parameter.

1 Like