The image manipulation code makes a photo into the felted fabric art.
A FabriCat example with the image from
Gray Tabby cat with Flowers
http://www.photos-public-domain.com/2010/09/03/gray-tabby-cat-with-flowers/
A RustiCat example with the image from
Calico Cat Closeup
http://www.photos-public-domain.com/2011/01/04/calico-cat-closeup/
Description of this image manipulation code.
This image manipulation code was written in Processing(Java) programming language.
This is the revised work of PsychedeliCat.
RustiCat makes a photo into the painting that is drawn with thin lines. FabriCat makes the painting with felted fabric.
You can use your own photo. You can hard coding the path of your photo image file or you can give command line parameter like this.
/your_processing_installed_path/processing-java --force --sketch=/your_sketch_path/ --run RustiCat /your_photo_file_path
If you want draw the Fabric one, then
/your_processing_installed_path/processing-java --force --sketch=/your_sketch_path/ --run FabriCat /your_photo_file_path
Example code of Processing.
Please feel free to use this example code under the terms of the GPL. To see other works based on my code is my pleasure. And my honor.
// RustiCat & FabriCat // @author @deconbatch // @version 0.2 // Processing 3.2.1 // 0.1 2017.06.03 // 0.2 2018.12.23 import java.util.Random; /* ---------------------------------------------------------------------- */ abstract class PshapeElement { PShape anElement; float elementColor, elementSaturation, elementBright, elementAlpha; float fctBrightMinus; PshapeElement() { anElement = pscreateElement(); elementColor = 0; elementSaturation = 0; elementBright = 0; elementAlpha = 0; fctBrightMinus = 0; } abstract PShape pscreateElement(); void setElementFill(float pcolor, float psaturation, float pbright, float palpha) { elementColor = pcolor; elementSaturation = psaturation; elementBright = pbright; elementAlpha = palpha; resetColor(); } void resetColor() { anElement.setStroke(color(elementColor, elementSaturation, elementBright, elementAlpha)); } void changeColor(float scolor) { elementColor = scolor; resetColor(); } void changeBright(float sbright) { elementBright = sbright; resetColor(); } void resetSize() { anElement.resetMatrix(); } void changeSize(float scaleX, float scaleY) { anElement.scale(scaleX, scaleY); } void rotate(float radX) { anElement.rotate(radX); } void show() { shape(anElement); } } /* ---------------------------------------------------------------------- */ class RoundBrush extends PshapeElement { RoundBrush() { super(); fctBrightMinus = 0.6; } PShape pscreateElement() { noFill(); PShape psDp = createShape(ELLIPSE, 0, 0, 6, 9); return psDp; } } /* ---------------------------------------------------------------------- */ class TriBrush extends PshapeElement { TriBrush() { super(); fctBrightMinus = 0.5; } PShape pscreateElement() { noFill(); PShape psDp = createShape( TRIANGLE, 0, -6, -4, 3, 4, 3 ); return psDp; } } /* ---------------------------------------------------------------------- */ class Utils { Random obj_random; Utils() { obj_random = new Random(); } float sigmoid(float x, float pcenter, float pgradient) { /** Sigmoid function 1.parameters. x : 1 > x value > 0 pcenter : center of curve. 1 - 1 / pcenter ex. 10 > pcenter > 1 : 0.9 > center of curve > 0 pgradient : gradient of curve. ex. 20 > pgradient > 4 : steep > gradient of curve > gentle 2.return. 1 > y value > 0 **/ float modx = pcenter * (x - 1) + 1; return (float)(1.0 / (1.0 + Math.exp(-modx * pgradient))); } float gaussdist(float pmean, float plimit, float pdevi) { /** Gaussian distribution 1.parameters. pmean : mean value plimit : max value of abs(deviation) ex. plimit >= 0 pmean = 0.5, plimit = 0.5 -> return value = from 0.0 to 1.0 pdevi : standard deviation value ex. good value? -> pdevi = plimit / 2 2.return. gaussian distribution **/ if (plimit == 0) { return pmean; } float gauss = (float) obj_random.nextGaussian() * pdevi; // not good idea if (abs(gauss) > plimit) { gauss = pow(plimit, 2) / gauss; } return pmean + gauss; } } /* ---------------------------------------------------------------------- */ Utils ut; PImage img; PshapeElement brush; int divX, divY; float minBright, maxBright, meanBright; float minSaturate, maxSaturate, meanSaturate; float canvasW, canvasH; float rateSize; void setup() { size(1080, 1080); float baseWH = 980; colorMode(HSB, 360, 100, 100, 100); blendMode(SCREEN); smooth(8); strokeWeight(0.000001); noLoop(); ut = new Utils(); if (args == null) { // you can use your photo in ./data/your_image.jpg img = loadImage("your_image.jpg"); } else { // args[0] must be image path img = loadImage(args[1]); } // fit to canvas size rateSize = baseWH / max(img.width, img.height); canvasW = img.width * rateSize; canvasH = img.height * rateSize; img.resize(int(canvasW), int(canvasH)); img.loadPixels(); // choose brush if (args[0].equals("FabriCat")) { println("FabriCat"); brush = new RoundBrush(); } else { println("RustiCat"); brush = new TriBrush(); } // divide 1 pixel divX = 1; divY = 1; minBright = 100; maxBright = 0; meanBright = 0; minSaturate = 100; maxSaturate = 0; meanSaturate = 0; int cntDivs = 0; for (int idxW = 0; idxW < img.width; idxW += divX) { for (int idxH = 0; idxH < img.height; idxH += divY) { float valBright = brightness(img.pixels[idxH * img.width + idxW]); minBright = minBright > valBright ? valBright : minBright; maxBright = maxBright < valBright ? valBright : maxBright; meanBright += valBright; float valSaturate = saturation(img.pixels[idxH * img.width + idxW]); minSaturate = minSaturate > valSaturate ? valSaturate : minSaturate; maxSaturate = maxSaturate < valSaturate ? valSaturate : maxSaturate; meanSaturate += valSaturate; ++cntDivs; } } meanBright = meanBright / cntDivs; meanSaturate = meanSaturate / cntDivs; } void draw() { background(0, 0, 0); translate((width - canvasW) / 2, (height - canvasH) / 2); float maxDistance = sqrt(pow(img.width/2, 2) + pow(img.height/2, 2)); float nsRX = random(50); float nsRY = random(50); float nsDX = random(50); float nsDY = random(50); float nfRX = nsRX; float nfDX = nsDX; for (int idxW = 0; idxW < img.width; idxW += divX) { float nfRY = nsRY; float nfDY = nsDY; for (int idxH = 0; idxH < img.height; idxH += divY) { float fctFray = ut.sigmoid(noise(nfDX, nfDY), 3.5, 4); float fctProbability = sqrt(pow(img.width/2 - idxW, 2) + pow(img.height/2 - idxH, 2)) / maxDistance; if (random(1) > map(fctFray * fctProbability, 0, 1, 0.15, 0.95)) { color c = img.pixels[idxH * img.width + idxW]; float valColor = setColor(c); float valSaturate = setSaturation(c); float valSize = 1.0 + fctProbability * map(fctFray, 0.0, 1.0, 0.5, map(valSaturate, 0.0, 100.0, 2.0, 18.0)); float valBright = setBrightness(c) - valSize * brush.fctBrightMinus; float valRotate = radians(180 * noise(nfRX, nfRY)); pushMatrix(); translate(idxW, idxH); brush.resetSize(); brush.changeSize(valSize, valSize); brush.rotate(valRotate); brush.setElementFill(valColor, valSaturate, valBright, 100); brush.show(); popMatrix(); } nfRY += 0.02; nfDY += 0.004; } nfRX += 0.02; nfDX += 0.004; } saveFrame("frames/####.png"); exit(); } float setColor(color c) { float divColor = map(saturation(c) + (50 - meanSaturate), 0, 100, 10, 30); return ut.gaussdist(hue(c) + 360, 20 + divColor, divColor) % 360; } float setSaturation(color c) { return min(100, 14 * sqrt(max(0, min(100, ut.gaussdist(saturation(c) + (50 - meanSaturate), 30, 10) - 5)))); } float setBrightness(color c) { return map(max(0, min(100, ut.gaussdist(brightness(c) + (50 - meanBright), 30, 10))), 0, 100, 10.0, 40.0); } /* Copyright (C) 2017- deconbatch This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/> */