Skip to content

Instantly share code, notes, and snippets.

@hysysk
Created August 26, 2013 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hysysk/6343445 to your computer and use it in GitHub Desktop.
Save hysysk/6343445 to your computer and use it in GitHub Desktop.
/*
Modified from SVG Stipple Generator, v. 2.02
Copyright (C) 2012 by Windell H. Oskay, www.evilmadscientist.com
Full Documentation: http://wiki.evilmadscience.com/StippleGen
Blog post about the release: http://www.evilmadscientist.com/go/stipple2
An implementation of Weighted Voronoi Stippling:
http://mrl.nyu.edu/~ajsecord/stipples.html
Inspired by Delaunay Raster http://jonathanpuckey.com/projects/delaunay-raster/
*******************************************************************************
/*
*
* This is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* http://creativecommons.org/licenses/LGPL/2.1/
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
import toxi.geom.*;
import toxi.geom.mesh2d.*;
int FRAME_RATE = 30;
String SOURCE_FILE_NAME = "img.jpg";
int MAX_PARTICLES_NUMBER = 1000;
float BRIGHTNESS_CUTOFF = 0;
int CELL_BUFFER = 200;
int CONTRAST = 10;
int MAIN_WIDTH = 800;
int MAIN_HEIGHT = 600;
int BORDER_WIDTH = 6;
int lowBorderX;
int highBorderX;
int lowBorderY;
int highBorderY;
int generation;
int voronoiPointsNumber;
boolean isVoronoiCalculated;
Voronoi voronoi;
Polygon2D regionList[];
PolygonClipper2D clip;
int totalCellsNumber;
int calculatedCellsNumber;
int lastCalculatedCellsNumber;
PImage img;
PImage imgLoad;
PImage imgBlur;
Vec2D[] particles;
void loadImageAndScale(String fileName, int targetWidth, int targetHeight) {
int tempX = 0;
int tempY = 0;
img = createImage(targetWidth, targetHeight, RGB);
imgBlur = createImage(targetWidth, targetHeight, RGB);
img.loadPixels();
for (int i = 0; i < img.pixels.length; i++) {
img.pixels[i] = color(255);
}
img.updatePixels();
imgLoad = loadImage(fileName);
if ((imgLoad.width > targetWidth) || (imgLoad.height > targetWidth)) {
if (((float)imgLoad.width / (float)imgLoad.height) > ((float)targetWidth / (float)targetHeight)) {
imgLoad.resize(targetWidth, 0);
}
else {
imgLoad.resize(0, targetHeight);
}
}
if (imgLoad.width < (targetWidth - 2)) {
tempX = (int)((targetWidth - imgLoad.width ) / 2) ;
}
if (imgLoad.height < (targetHeight - 2)) {
tempY = (int)((targetHeight - imgLoad.height ) / 2) ;
}
img.copy(imgLoad, 0, 0, imgLoad.width, imgLoad.height, tempX, tempY, imgLoad.width, imgLoad.height);
imgBlur.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
imgBlur.filter(BLUR, 1);
imgBlur.loadPixels();
}
void setupMainArray(int startX, int startY, int lengthX, int lengthY) {
particles = new Vec2D[MAX_PARTICLES_NUMBER];
int i = 0;
float x;
float y;
float b;
while (i < MAX_PARTICLES_NUMBER) {
x = startX + random(lengthX);
y = startY + random(lengthY);
b = brightness(imgBlur.pixels[floor(y)*imgBlur.width + floor(x)])/255;
if (random(1) >= b) {
Vec2D p = new Vec2D(x, y);
particles[i] = p;
i++;
}
}
generation = 0;
isVoronoiCalculated = false;
calculatedCellsNumber = 0;
voronoiPointsNumber = 0;
voronoi = new Voronoi();
}
void setup() {
size(MAIN_WIDTH, MAIN_HEIGHT, P2D);
lowBorderX = BORDER_WIDTH;
highBorderX = MAIN_WIDTH - BORDER_WIDTH;
lowBorderY = BORDER_WIDTH;
highBorderY = MAIN_HEIGHT - BORDER_WIDTH;
int innerWidth = MAIN_WIDTH - 2 * BORDER_WIDTH;
int innerHeight = MAIN_HEIGHT - 2 * BORDER_WIDTH;
clip = new SutherlandHodgemanClipper(new Rect(lowBorderX, lowBorderY, innerWidth, innerHeight));
loadImageAndScale(SOURCE_FILE_NAME, MAIN_WIDTH, MAIN_HEIGHT);
setupMainArray(lowBorderX, lowBorderY, innerWidth, innerHeight);
frameRate(FRAME_RATE);
noStroke();
smooth();
}
void draw() {
updateParticles();
drawDelaunay();
}
void updateParticles() {
if (!isVoronoiCalculated) {
calculateVoronoi();
}
else {
calculateCells();
}
}
void calculateVoronoi() {
if (voronoiPointsNumber == 0) {
voronoi = new Voronoi();
}
int tempPointsNumber = voronoiPointsNumber + 100;
if (tempPointsNumber > MAX_PARTICLES_NUMBER) {
tempPointsNumber = MAX_PARTICLES_NUMBER;
}
int i;
for (i = voronoiPointsNumber; i < tempPointsNumber; i++) {
voronoi.addPoint(new Vec2D(particles[i].x, particles[i].y));
voronoiPointsNumber++;
}
if (voronoiPointsNumber >= MAX_PARTICLES_NUMBER) {
totalCellsNumber = voronoi.getRegions().size();
voronoiPointsNumber = 0;
calculatedCellsNumber = 0;
lastCalculatedCellsNumber = 0;
regionList = new Polygon2D[totalCellsNumber];
i = 0;
for (Polygon2D poly : voronoi.getRegions()) {
regionList[i++] = poly;
}
isVoronoiCalculated = true;
}
}
void calculateCells() {
int tempCellsNumber = calculatedCellsNumber + 50;
if (tempCellsNumber > totalCellsNumber) {
tempCellsNumber = totalCellsNumber;
}
float maxX = 0;
float minX = MAIN_WIDTH;
float maxY = 0;
float minY = MAIN_HEIGHT;
float tx, ty;
for (int i=calculatedCellsNumber; i < tempCellsNumber; i++) {
Polygon2D region = clip.clipPolygon(regionList[i]);
for (Vec2D v : region.vertices) {
tx = v.x;
ty = v.y;
if (tx < minX)
minX = tx;
if (tx > maxX)
maxX = tx;
if (ty < minY)
minY = ty;
if (ty > maxY)
maxY = ty;
}
float diffX = maxX - minX;
float diffY = maxY - minY;
float maxSize = max(diffX, diffY);
float minSize = min(diffX, diffY);
float scaleFactor = 1.0;
while (maxSize > CELL_BUFFER) {
scaleFactor *= 0.5;
maxSize *= 0.5;
}
while (maxSize < (CELL_BUFFER/2)) {
scaleFactor *= 2;
maxSize *= 2;
}
if ((minSize * scaleFactor) > (CELL_BUFFER/2)) {
scaleFactor *= 0.5;
}
float stepSize = (1/scaleFactor);
float sumX = 0;
float sumY = 0;
float sumD = 0;
float pictureDensity = 1.0;
for (float x=minX; x<=maxX; x+=stepSize) {
for (float y=minY; y<=maxY; y+=stepSize) {
Vec2D p0 = new Vec2D(x, y);
if (region.containsPoint(p0)) {
pictureDensity = 255.001 - (brightness(imgBlur.pixels[ round(y)*imgBlur.width + round(x) ]));
sumX += pictureDensity * x;
sumY += pictureDensity * y;
sumD += pictureDensity;
}
}
}
if (sumD > 0) {
sumX /= sumD;
sumY /= sumD;
}
Vec2D center;
float tempX = sumX;
float tempY = sumY;
if ((tempX <= lowBorderX) || (tempX >= highBorderX) || (tempY <= lowBorderY) || (tempY >= highBorderY)) {
center = region.getCentroid();
tempX = center.x;
tempY = center.y;
if (tempX <= lowBorderX)
tempX = lowBorderX + 1;
if (tempX >= highBorderX)
tempX = highBorderX - 1;
if (tempY <= lowBorderY)
tempY = lowBorderY + 1;
if (tempY >= highBorderY)
tempY = highBorderY - 1;
}
particles[i].x = tempX;
particles[i].y = tempY;
calculatedCellsNumber++;
}
if (calculatedCellsNumber >= totalCellsNumber) {
isVoronoiCalculated = false;
generation++;
}
}
void drawDelaunay() {
background(255);
beginShape(TRIANGLE);
int r, g, b;
color c;
Vec2D v;
int ca, cb, cc;
int brightness;
for (Triangle2D t : voronoi.getTriangles()) {
if (!isInsideBounds(t.a, t.b, t.c)) continue;
v = t.computeCentroid();
c = imgBlur.get((int)v.x, (int)v.y);
r = (int)red(c);
g = (int)green(c);
b = (int)blue(c);
fill(r + CONTRAST, g + CONTRAST, b + CONTRAST);
vertex(t.a.x, t.a.y);
fill(r, g, b);
vertex(t.b.x, t.b.y);
fill(r - CONTRAST, g - CONTRAST, b - CONTRAST);
vertex(t.c.x, t.c.y);
}
endShape();
}
boolean isInsideBounds(Vec2D a, Vec2D b, Vec2D c) {
if (a.x < 0 || a.x > width || a.y < 0 || a.y > height ||
b.x < 0 || b.y < 0 || b.x > width || b.y > height ||
c.x < 0 || c.y < 0 || c.x > width || c.y > height) {
return false;
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment