Created
August 26, 2013 16:25
-
-
Save hysysk/6343445 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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