diff --git a/InkscapePortable/Data/settings/Dropbox/shellext/l/53394bee b/InkscapePortable/Data/settings/Dropbox/shellext/l/53394bee
new file mode 100644
index 0000000..b0dc32c
Binary files /dev/null and b/InkscapePortable/Data/settings/Dropbox/shellext/l/53394bee differ
diff --git a/InkscapePortable/Data/settings/InkscapePortableSettings.ini b/InkscapePortable/Data/settings/InkscapePortableSettings.ini
index 0f95f41..5ec00b4 100644
--- a/InkscapePortable/Data/settings/InkscapePortableSettings.ini
+++ b/InkscapePortable/Data/settings/InkscapePortableSettings.ini
@@ -1,7 +1,7 @@
[InkscapePortableSettings]
-LastDrive=G:
-LastDirectory=\A_PROJECTS\Spherebot-Host-GUI V2.0\InkscapePortable
+LastDrive=F:
+LastDirectory=\User-Daten\Documents\GitHub\Spherebot-Host-GUI\InkscapePortable
[PortableApps.comLauncherLastRunEnvironment]
-PAL:LastPortableAppsBaseDir=G:\A_PROJECTS
+PAL:LastPortableAppsBaseDir=F:\User-Daten\Documents\GitHub
[Language]
LANG=de
diff --git a/InkscapePortable/Data/settings/preferences.xml b/InkscapePortable/Data/settings/preferences.xml
index 7857745..29b5142 100644
--- a/InkscapePortable/Data/settings/preferences.xml
+++ b/InkscapePortable/Data/settings/preferences.xml
@@ -553,7 +553,16 @@
w="427"
h="432" />
+ id="filtereffects"
+ panel_size="1"
+ panel_ratio="100"
+ panel_mode="1"
+ panel_wrap="0"
+ state="2"
+ x="0"
+ y="0"
+ w="507"
+ h="400" />
+ w="1037"
+ h="959" />
+ path="F:\User-Daten\Documents\GitHub\Documents\Pictures\Spherebot" />
+ path="F:\User-Daten\Desktop\" />
-
+
+ y="0"
+ x="0"
+ state="2"
+ panel_wrap="0"
+ panel_mode="1"
+ panel_ratio="100"
+ panel_size="1"
+ id="documentmetadata" />
@@ -1036,10 +1045,10 @@
id="desktop"
style="fill:#000000;fill-opacity:1;-inkscape-font-specification:Wide Latin;font-family:Wide Latin;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1">
diff --git a/Spherebot_UI/Spherebot_UI.pro.user b/Spherebot_UI/Spherebot_UI.pro.user
index e3037e4..57a5811 100644
--- a/Spherebot_UI/Spherebot_UI.pro.user
+++ b/Spherebot_UI/Spherebot_UI.pro.user
@@ -1,6 +1,6 @@
-
+
ProjectExplorer.Project.ActiveTarget
@@ -111,7 +111,7 @@
true
- G:/A_PROJECTS/Spherebot-Host-GUI V2.0/build-Spherebot_UI-Desktop_Qt_5_2_1_MinGW_32bit-Release
+ F:/User-Daten/Documents/GitHub/Spherebot-Host-GUI/build
true
diff --git a/Spherebot_UI/bin/Spherebot_UI.exe b/Spherebot_UI/bin/Spherebot_UI.exe
index 4040776..4c6fc5c 100644
Binary files a/Spherebot_UI/bin/Spherebot_UI.exe and b/Spherebot_UI/bin/Spherebot_UI.exe differ
diff --git a/Spherebot_UI/mainwindow.cpp b/Spherebot_UI/mainwindow.cpp
index 5cf82b4..c9b2734 100644
--- a/Spherebot_UI/mainwindow.cpp
+++ b/Spherebot_UI/mainwindow.cpp
@@ -29,6 +29,7 @@ MainWindow::MainWindow(QWidget *parent) :
connect(&Transceiver,SIGNAL(progressChanged(int)),this,SLOT(refreshSendProgress(int)));
connect(&Transceiver,SIGNAL(fileTransmitted()),this,SLOT(finishedTransmission()));
connect(this->bot,SIGNAL(dataSent(QString)),this,SLOT(interpretSentString(QString)));
+ connect(&Transceiver,SIGNAL(layerTransmitted()),this,SLOT(finishedLayer()));
initUI();
@@ -40,17 +41,6 @@ MainWindow::MainWindow(QWidget *parent) :
FitInTimer.start();
}
qDebug()<<"mainwindow initialised: ";
-
-// if (QtWin::isCompositionEnabled()) {
-// QtWin::extendFrameIntoClientArea(this, -1, -1, -1, -1);
-// setAttribute(Qt::WA_TranslucentBackground, true);
-// setAttribute(Qt::WA_NoSystemBackground, false);
-// setStyleSheet("MusicPlayer { background: transparent; }");
-// } else {
-// QtWin::resetExtendedFrame(this);
-// setAttribute(Qt::WA_TranslucentBackground, false);
-// setStyleSheet(QString("MusicPlayer { background: %1; }").arg(QtWin::realColorizationColor().name()));
-// }
}
void MainWindow::fitgraphicsView() ////function to trigger the fitIn function for the graphics view. Actually this shouldn´t be necessary!
@@ -75,8 +65,8 @@ void MainWindow::initUI()
"Restart?",
"Do you want to restart the print?",
QMessageBox::Yes|QMessageBox::No);
- restartLayerMsgBox->setButtonText(QMessageBox::Yes,"OK");
- restartLayerMsgBox->setButtonText(QMessageBox::No,"Abort");
+ //restartLayerMsgBox->setButtonText(QMessageBox::Yes,"OK");
+ //restartLayerMsgBox->setButtonText(QMessageBox::No,"No");
}
MainWindow::~MainWindow()
@@ -99,7 +89,7 @@ bool MainWindow::LoadSettings()
if(QFile::exists(curFile))
{
qDebug()<<"load last file.";
- loadFileAndSubFiles(curFile);
+ loadFile(curFile);
returnvalue = true;
}
}
@@ -175,6 +165,7 @@ void MainWindow::loadFile(const QString &fileName)
QString code = file.readAll();
extractOptions(code);
interpretGcode(code);
+ refreshLayerNames(code);
ui->fileTextEdit->setText(code);
qDebug()<sendButton->setEnabled(false);
}
-
-void MainWindow::loadFileAndSubFiles(const QString &fileName) //SubFiles = other layers of the print
-
-{
- loadFile(fileName);
- layerFileNames.clear();
- layerFileNames.append(fileName);
- //search for other layer files
- QStringList nameParts = fileName.split("_");
- QString layerNumberString = nameParts.last();
- layerNumberString.chop(6); //remove .gcode
- int layerNumber = layerNumberString.toInt();
- nameParts.removeLast();
- QString mainName = nameParts.join("");
- QFile testFile;
- if(layerNumber != 0) //Conversion successfully
- {
- qDebug()<<"layerNumber is unequal 0";
- while(1)
- {
- qDebug()<<"loop: "< lines = file.split("\n");
+ int layerchange = 0;
+ for(int i=0;iport->canReadLine())
@@ -289,13 +259,39 @@ void MainWindow::receiveData()
}
}
-
-
void MainWindow::refreshSendProgress(int value)
{
ui->fileSendProgressBar->setValue(value);
}
+void MainWindow::finishedLayer()
+{
+ if(layerNames.size() > 1)
+ {
+ layerIndex++;
+ }
+ qDebug()<<"layerIndex: "<setText("Please change the tool for layer: " + layerNames[layerIndex]);
+ SetBotToHomePosition();
+ switch(nextLayerMsgBox->exec())
+ {
+ case(QMessageBox::No):
+ setState(Idle); //abort
+ break;
+ case(QMessageBox::Yes):
+ break;
+ default:
+ setState(Idle); //abort
+ break;
+ }
+
+ }
+}
+
void MainWindow::finishedTransmission()
{
disconnectTranceiver();
@@ -308,43 +304,11 @@ void MainWindow::finishedTransmission()
ui->loadFileButton->setEnabled(true);
statusBar()->showMessage(tr("File successfully sent"));
sendState = Idle;
- if(layerFileNames.size() > 1)
+ layerIndex = 0;
+ SetBotToHomePosition();
+ if (QMessageBox::Yes == restartLayerMsgBox->exec())
{
- layerIndex++;
- }
- qDebug()<<"layerIndex: "<exec())
- {
- if(layerFileNames.size() >= layerIndex)
- {
- loadFile(layerFileNames.at(layerIndex));
- on_sendFileButton_clicked();
- }
- else
- {
- qDebug()<<"Warning: tried to loadFile to start next layer but layerIndex is too high!";
- }
- }
- }
- else //restart print?
- {
- layerIndex = 0;
- if(!layerFileNames.isEmpty())
- {
- loadFile(layerFileNames.at(layerIndex));
- }
- else
- {
- qDebug()<<"Warning: tried to loadFile to restart print but the layerFileNames are empty!";
- }
- if (QMessageBox::Yes == restartLayerMsgBox->exec())
- {
- on_sendFileButton_clicked();
- }
+ on_sendFileButton_clicked();
}
}
@@ -527,9 +491,9 @@ void MainWindow::on_eggRotationBox_valueChanged(int arg1)
}
}
-void MainWindow::on_loadFileButton_clicked()
+void MainWindow::on_loadFileButton_clicked() //== Abort Button
{
- if(sendState != Stoped)
+ if(sendState == Idle)
{
QString fileName;
if(!curDir.isEmpty())
@@ -542,7 +506,7 @@ void MainWindow::on_loadFileButton_clicked()
}
if (!fileName.isEmpty())
{
- loadFileAndSubFiles(fileName);
+ loadFile(fileName);
}
}
else
@@ -578,6 +542,7 @@ void MainWindow::connectTranceiver()
void MainWindow::disconnectTranceiver()
{
disconnect(this->bot->port,SIGNAL(readyRead()),(&this->Transceiver),SLOT(sendNext()));
+ this->Transceiver.resetState();
}
void MainWindow::setState(MainWindow::SendStates state)
@@ -587,7 +552,10 @@ void MainWindow::setState(MainWindow::SendStates state)
case(Idle):
switch(sendState)
{
- case(Stoped): //abort print
+ case(Sending): //from Sending to Idle
+ //same code as below
+ ui->fileSendProgressBar->setValue(0);
+ case(Stoped): //from Stoped to Idle
disconnectTranceiver();
ui->sendFileButton->setText("Send File");
ui->loadFileButton->setText("Load File");
@@ -597,15 +565,12 @@ void MainWindow::setState(MainWindow::SendStates state)
ui->loadFileButton->setEnabled(true);
sendState = Idle;
break;
- case(Sending):
- //not allowed to set to idle while sending. A stop is necessary
- break;
}
break;
case(Sending):
switch(sendState)
{
- case(Idle): //start sending
+ case(Idle): //start sending //from Idle to Sending
sendState = Sending;
connectTranceiver();
ui->controllBox->setEnabled(false);
@@ -641,13 +606,13 @@ void MainWindow::setState(MainWindow::SendStates state)
break;
}
break;
- case(Stoped):
+ case(Stoped): //from Stoped to
switch(sendState)
{
case(Idle):
break;
- case(Sending):
+ case(Sending): //from Sending to Stoped
sendState = Stoped;
disconnectTranceiver();
ui->restartButton->setEnabled(true);
@@ -703,6 +668,14 @@ void MainWindow::on_restartButton_clicked()
statusBar()->showMessage(tr("Sending File"));
}
+void MainWindow::SetBotToHomePosition()
+{
+ QString tmp = ("G1 Y0");
+ tmp.append("\nM300 S" + QString::number(penUpAngle));
+ qDebug()<<"to print: "<send(tmp);
+}
+
void MainWindow::on_servoFeedrateSlider_valueChanged(int value)
{
if(sendState != Sending)
diff --git a/Spherebot_UI/mainwindow.h b/Spherebot_UI/mainwindow.h
index d32d4b5..05aeddc 100644
--- a/Spherebot_UI/mainwindow.h
+++ b/Spherebot_UI/mainwindow.h
@@ -3,7 +3,6 @@
#include
#include "spherebot.h"
-//#include "qextserialenumerator.h"
#include
#include
#include
@@ -50,6 +49,7 @@ public slots:
void finishedTransmission();
void refreshSendProgress(int value);
void fitgraphicsView();
+ void finishedLayer();
private slots:
void on_connectButton_clicked();
@@ -86,6 +86,8 @@ private:
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
void interpretGcode(QString code);
+ void refreshLayerNames(QString file);
+ void SetBotToHomePosition();
QString curFile;
QString curDir;
SendStates sendState;
@@ -97,7 +99,7 @@ private:
QTimer *rxTimer;
txThread Transceiver;
QGraphicsScene *scene;
- QList layerFileNames; //layerFile, layerColorString
+ QList layerNames; //layerFile, layerColorString
int layerIndex;
QMessageBox *nextLayerMsgBox;
diff --git a/Spherebot_UI/res.qrc b/Spherebot_UI/res.qrc
index 3588f91..706bf7f 100644
--- a/Spherebot_UI/res.qrc
+++ b/Spherebot_UI/res.qrc
@@ -2,7 +2,5 @@
stylesheet.txt
-
- icon.png
-
+
diff --git a/Spherebot_UI/txthread.cpp b/Spherebot_UI/txthread.cpp
index 66cd190..734c94b 100644
--- a/Spherebot_UI/txthread.cpp
+++ b/Spherebot_UI/txthread.cpp
@@ -2,7 +2,7 @@
txThread::txThread()
{
- lineCounter = 0;
+ resetState();
}
txThread::~txThread()
@@ -10,6 +10,12 @@ txThread::~txThread()
}
+void txThread::resetState()
+{
+ lineCounter = 0;
+ ignoreFirstM01 = true;
+}
+
QString removeComments(QString intext)
{
////////////////////////////////////////////////remove comments
@@ -32,7 +38,7 @@ QString removeComments(QString intext)
}
}
}
- ///////////////////////////////////////////////////
+ ///////////////////////////////////////////////////
return outTmp2;
}
@@ -41,7 +47,7 @@ void txThread::set(QString intextfile,spherebot &uibot)
lineCounter = 0;
textfile.clear();
textfile.append(removeComments(intextfile));
- qDebug()<<"The textfile String is: \n\n" + textfile + "\n\nENDE\n\n";
+ //qDebug()<<"The textfile String is: \n\n" + textfile + "\n\nENDE\n\n";
lineMax = textfile.count("\n");
bot = &uibot;
}
@@ -59,6 +65,17 @@ void txThread::sendNext()
if(lineCounter <= lineMax)
{
tmp = textfile.section("\n",lineCounter,lineCounter);
+ if(tmp.contains("M01"))
+ {
+ if(ignoreFirstM01)
+ {
+ ignoreFirstM01 = false;
+ }
+ else
+ {
+ emit layerTransmitted();
+ }
+ }
tmp.append("\n");
bot->send(tmp);
double progress= (double) lineCounter/(double)lineMax;
@@ -68,6 +85,7 @@ void txThread::sendNext()
else
{
emit fileTransmitted();
+ resetState();
return;
}
if(tmp.contains("G4"))
diff --git a/Spherebot_UI/txthread.h b/Spherebot_UI/txthread.h
index 3c72357..2085853 100644
--- a/Spherebot_UI/txthread.h
+++ b/Spherebot_UI/txthread.h
@@ -5,6 +5,7 @@
#include "spherebot.h"
#include
#include
+#include
class txThread : public QThread
{
@@ -18,14 +19,17 @@ public:
signals:
void progressChanged(int);
+ void layerTransmitted();
void fileTransmitted();
public slots:
void sendNext();
+ void resetState();
private:
QString textfile;
int lineCounter;
int lineMax;
+ bool ignoreFirstM01;
spherebot *bot;
};
diff --git a/StippleGen_2.02/StippleGen_2/README.txt b/StippleGen_2.02/StippleGen_2/README.txt
new file mode 100644
index 0000000..bbbea37
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/README.txt
@@ -0,0 +1,71 @@
+
+ StippleGen_2
+
+ 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
+
+
+ *******************************************************************************
+
+ Change Log:
+
+ v 2.02
+ * Force files to end in .svg
+ * Fix bug that gave wrong size to stipple files saved white stipples on black background
+
+ v 2.01:
+ * Improved handling of Save process, to prevent accidental "not saving" by users.
+
+ v 2.0:
+ * Add tone reversal option (white on black / black on white)
+ * Reduce vertical extent of GUI, to reduce likelihood of cropping on small screens
+ * Speling corections
+ * Fixed a bug that caused unintended cropping of long, wide images
+ * Reorganized GUI controls
+ * Fail less disgracefully when a bad image type is selected.
+
+ *******************************************************************************
+
+ Program is based on the Toxic Libs Library ( http://toxiclibs.org/ )
+ & example code:
+ http://forum.processing.org/topic/toxiclib-voronoi-example-sketch
+
+
+ Additional inspiration:
+ Stipple Cam from Jim Bumgardner
+ http://joyofprocessing.com/blog/2011/11/stipple-cam/
+
+ and
+
+ MeshLibDemo.pde - Demo of Lee Byron's Mesh library, by
+ Marius Watz - http://workshop.evolutionzone.com/
+
+
+ Requires ControlP5 library and Toxic Libs library:
+ http://www.sojamo.de/libraries/controlP5/
+ http://hg.postspectacular.com/toxiclibs/downloads
+
+
+
+ * 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
\ No newline at end of file
diff --git a/StippleGen_2.02/StippleGen_2/StippleGen_2.pde b/StippleGen_2.02/StippleGen_2/StippleGen_2.pde
new file mode 100644
index 0000000..40c0771
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/StippleGen_2.pde
@@ -0,0 +1,1373 @@
+/**
+
+ StippleGen_2
+
+ 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
+
+ *******************************************************************************
+
+ Change Log:
+
+ v 2.02
+ * Force files to end in .svg
+ * Fix bug that gave wrong size to stipple files saved white stipples on black background
+
+ v 2.01:
+ * Improved handling of Save process, to prevent accidental "not saving" by users.
+
+ v 2.0:
+ * Add tone reversal option (white on black / black on white)
+ * Reduce vertical extent of GUI, to reduce likelihood of cropping on small screens
+ * Speling corections
+ * Fixed a bug that caused unintended cropping of long, wide images
+ * Reorganized GUI controls
+ * Fail less disgracefully when a bad image type is selected.
+
+ *******************************************************************************
+
+
+
+ Program is based on the Toxic Libs Library ( http://toxiclibs.org/ )
+ & example code:
+ http://forum.processing.org/topic/toxiclib-voronoi-example-sketch
+
+
+ Additional inspiration:
+ Stipple Cam from Jim Bumgardner
+ http://joyofprocessing.com/blog/2011/11/stipple-cam/
+
+ and
+
+ MeshLibDemo.pde - Demo of Lee Byron's Mesh library, by
+ Marius Watz - http://workshop.evolutionzone.com/
+
+
+ Requires ControlP5 library and Toxic Libs library:
+ http://www.sojamo.de/libraries/controlP5/
+ http://hg.postspectacular.com/toxiclibs/downloads
+
+
+ */
+
+
+/*
+ *
+ * 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
+ */
+
+
+// You need the controlP5 library from http://www.sojamo.de/libraries/controlP5/
+import controlP5.*;
+
+//You need the Toxic Libs library: http://hg.postspectacular.com/toxiclibs/downloads
+
+import toxi.geom.*;
+import toxi.geom.mesh2d.*;
+import toxi.util.datatypes.*;
+import toxi.processing.*;
+
+// helper class for rendering
+ToxiclibsSupport gfx;
+
+import javax.swing.UIManager;
+import javax.swing.JFileChooser;
+
+
+
+
+// Feel free to play with these three default settings:
+int maxParticles = 2000; // Max value is normally 10000. Press 'x' key to allow 50000 stipples. (SLOW)
+float MinDotSize = 1.75; //2;
+float DotSizeFactor = 4; //5;
+float cutoff = 0; // White cutoff value
+
+
+int cellBuffer = 100; //Scale each cell to fit in a cellBuffer-sized square window for computing the centroid.
+
+
+// Display window and GUI area sizes:
+int mainwidth;
+int mainheight;
+int borderWidth;
+int ctrlheight;
+int TextColumnStart;
+
+
+
+float lowBorderX;
+float hiBorderX;
+float lowBorderY;
+float hiBorderY;
+
+
+
+float MaxDotSize;
+boolean ReInitiallizeArray;
+boolean pausemode;
+boolean fileLoaded;
+int SaveNow;
+String savePath;
+String[] FileOutput;
+
+
+
+
+String StatusDisplay = "Initializing, please wait. :)";
+float millisLastFrame = 0;
+float frameTime = 0;
+
+String ErrorDisplay = "";
+float ErrorTime;
+Boolean ErrorDisp = false;
+
+
+int Generation;
+int particleRouteLength;
+int RouteStep;
+
+boolean showBG;
+boolean showPath;
+boolean showCells;
+boolean invertImg;
+boolean TempShowCells;
+boolean FileModeTSP;
+
+int vorPointsAdded;
+boolean VoronoiCalculated;
+
+// Toxic libs library setup:
+Voronoi voronoi;
+Polygon2D RegionList[];
+
+PolygonClipper2D clip; // polygon clipper
+
+int cellsTotal, cellsCalculated, cellsCalculatedLast;
+
+
+// ControlP5 GUI library variables setup
+Textlabel ProgName;
+Button OrderOnOff, ImgOnOff, CellOnOff, InvertOnOff, PauseButton;
+ControlP5 cp5;
+
+
+PImage img, imgload, imgblur;
+
+Vec2D[] particles;
+int[] particleRoute;
+
+
+
+void LoadImageAndScale() {
+
+ int tempx = 0;
+ int tempy = 0;
+
+ img = createImage(mainwidth, mainheight, RGB);
+ imgblur = createImage(mainwidth, mainheight, RGB);
+
+ img.loadPixels();
+
+ if (invertImg)
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(0);
+ }
+ else
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(255);
+ }
+
+ img.updatePixels();
+
+ if ( fileLoaded == false) {
+ // Load a demo image, at least until we have a "real" image to work with.
+
+ imgload = loadImage("grace.jpg"); // Load demo image
+ // Image source: http://commons.wikimedia.org/wiki/File:Kelly,_Grace_(Rear_Window).jpg
+ }
+
+ if ((imgload.width > mainwidth) || (imgload.height > mainheight)) {
+
+ if (((float) imgload.width / (float)imgload.height) > ((float) mainwidth / (float) mainheight))
+ {
+ imgload.resize(mainwidth, 0);
+ }
+ else
+ {
+ imgload.resize(0, mainheight);
+ }
+ }
+
+ if (imgload.height < (mainheight - 2) ) {
+ tempy = (int) (( mainheight - imgload.height ) / 2) ;
+ }
+ if (imgload.width < (mainwidth - 2)) {
+ tempx = (int) (( mainwidth - imgload.width ) / 2) ;
+ }
+
+ img.copy(imgload, 0, 0, imgload.width, imgload.height, tempx, tempy, imgload.width, imgload.height);
+ // For background image!
+
+
+ /*
+ // Optional gamma correction for background image.
+ img.loadPixels();
+
+ float tempFloat;
+ float GammaValue = 1.0; // Normally in the range 0.25 - 4.0
+
+ for (int i = 0; i < img.pixels.length; i++) {
+ tempFloat = brightness(img.pixels[i])/255;
+ img.pixels[i] = color(floor(255 * pow(tempFloat,GammaValue)));
+ }
+ img.updatePixels();
+ */
+
+
+ imgblur.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
+ // This is a duplicate of the background image, that we will apply a blur to,
+ // to reduce "high frequency" noise artifacts.
+
+ imgblur.filter(BLUR, 1); // Low-level blur filter to elminate pixel-to-pixel noise artifacts.
+ imgblur.loadPixels();
+}
+
+
+void MainArraysetup() {
+ // Main particle array initialization (to be called whenever necessary):
+
+ LoadImageAndScale();
+
+ // image(img, 0, 0); // SHOW BG IMG
+
+ particles = new Vec2D[maxParticles];
+
+
+ // Fill array by "rejection sampling"
+ int i = 0;
+ while (i < maxParticles)
+ {
+
+ float fx = lowBorderX + random(hiBorderX - lowBorderX);
+ float fy = lowBorderY + random(hiBorderY - lowBorderY);
+
+ float p = brightness(imgblur.pixels[ floor(fy)*imgblur.width + floor(fx) ])/255;
+ // OK to use simple floor_ rounding here, because this is a one-time operation,
+ // creating the initial distribution that will be iterated.
+
+ if (invertImg)
+ {
+ p = 1 - p;
+ }
+
+ if (random(1) >= p ) {
+ Vec2D p1 = new Vec2D(fx, fy);
+ particles[i] = p1;
+ i++;
+ }
+ }
+
+ particleRouteLength = 0;
+ Generation = 0;
+ millisLastFrame = millis();
+ RouteStep = 0;
+ VoronoiCalculated = false;
+ cellsCalculated = 0;
+ vorPointsAdded = 0;
+ voronoi = new Voronoi(); // Erase mesh
+ TempShowCells = true;
+ FileModeTSP = false;
+}
+
+void setup()
+{
+
+ borderWidth = 6;
+
+ mainwidth = 800;
+ mainheight = 600;
+ ctrlheight = 110;
+
+ size(mainwidth, mainheight + ctrlheight, JAVA2D);
+
+ gfx = new ToxiclibsSupport(this);
+
+
+ lowBorderX = borderWidth; //mainwidth*0.01;
+ hiBorderX = mainwidth - borderWidth; //mainwidth*0.98;
+ lowBorderY = borderWidth; // mainheight*0.01;
+ hiBorderY = mainheight - borderWidth; //mainheight*0.98;
+
+ int innerWidth = mainwidth - 2 * borderWidth;
+ int innerHeight = mainheight - 2 * borderWidth;
+
+ clip=new SutherlandHodgemanClipper(new Rect(lowBorderX, lowBorderY, innerWidth, innerHeight));
+
+ MainArraysetup(); // Main particle array setup
+
+ frameRate(24);
+
+ smooth();
+ noStroke();
+ fill(153); // Background fill color, for control section
+
+ textFont(createFont("SansSerif", 10));
+
+
+ cp5 = new ControlP5(this);
+
+ int leftcolumwidth = 225;
+
+ int GUItop = mainheight + 15;
+ int GUI2ndRow = 4; // Spacing for firt row after group heading
+ int GuiRowSpacing = 14; // Spacing for subsequent rows
+ int GUIFudge = mainheight + 19; // I wish that we didn't need ONE MORE of these stupid spacings.
+
+
+ ControlGroup l3 = cp5.addGroup("Primary controls (Changing will restart)", 10, GUItop, 225);
+
+ cp5.addSlider("Stipples", 10, 10000, maxParticles, 10, GUI2ndRow, 150, 10).setGroup(l3);
+
+ InvertOnOff = cp5.addButton("INVERT_IMG", 10, 10, GUI2ndRow + GuiRowSpacing, 190, 10).setGroup(l3);
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+
+
+ Button LoadButton = cp5.addButton("LOAD_FILE", 10, 10, GUIFudge + 3*GuiRowSpacing, 175, 10);
+ LoadButton.setCaptionLabel("LOAD IMAGE FILE (.PNG, .JPG, or .GIF)");
+
+ cp5.addButton("QUIT", 10, 205, GUIFudge + 3*GuiRowSpacing, 30, 10);
+
+ cp5.addButton("SAVE_STIPPLES", 10, 25, GUIFudge + 4*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_STIPPLES").setCaptionLabel("Save Stipple File (.SVG format)");
+
+ cp5.addButton("SAVE_PATH", 10, 25, GUIFudge + 5*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_PATH").setCaptionLabel("Save \"TSP\" Path (.SVG format)");
+
+
+ ControlGroup l5 = cp5.addGroup("Display Options - Updated on next generation", leftcolumwidth+50, GUItop, 225);
+
+ cp5.addSlider("Min_Dot_Size", .5, 8, 2, 10, 4, 140, 10).setGroup(l5);
+ cp5.controller("Min_Dot_Size").setValue(MinDotSize);
+ cp5.controller("Min_Dot_Size").setCaptionLabel("Min. Dot Size");
+
+ cp5.addSlider("Dot_Size_Range", 0, 20, 5, 10, 18, 140, 10).setGroup(l5);
+ cp5.controller("Dot_Size_Range").setValue(DotSizeFactor);
+ cp5.controller("Dot_Size_Range").setCaptionLabel("Dot Size Range");
+
+ cp5.addSlider("White_Cutoff", 0, 1, 0, 10, 32, 140, 10).setGroup(l5);
+ cp5.controller("White_Cutoff").setValue(cutoff);
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+
+
+ ImgOnOff = cp5.addButton("IMG_ON_OFF", 10, 10, 46, 90, 10);
+ ImgOnOff.setGroup(l5);
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+
+ CellOnOff = cp5.addButton("CELLS_ON_OFF", 10, 110, 46, 90, 10);
+ CellOnOff.setGroup(l5);
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+
+ PauseButton = cp5.addButton("Pause", 10, 10, 60, 190, 10);
+ PauseButton.setGroup(l5);
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+
+ OrderOnOff = cp5.addButton("ORDER_ON_OFF", 10, 10, 74, 190, 10);
+ OrderOnOff.setGroup(l5);
+ OrderOnOff.setCaptionLabel("Plotting path >> shown while paused");
+
+
+
+
+
+ TextColumnStart = 2 * leftcolumwidth + 100;
+
+ MaxDotSize = MinDotSize * (1 + DotSizeFactor);
+
+ ReInitiallizeArray = false;
+ pausemode = false;
+ showBG = false;
+ invertImg = false;
+ showPath = true;
+ showCells = false;
+ fileLoaded = false;
+ SaveNow = 0;
+}
+
+
+void LOAD_FILE(float theValue) {
+ println(":::LOAD JPG, GIF or PNG FILE:::");
+
+ String loadPath = selectInput(); // Opens file chooser
+ if (loadPath == null) {
+ // If a file was not selected
+ println("No file was selected...");
+ }
+ else {
+ // If a file was selected, print path to file
+ println("Loaded file: " + loadPath);
+
+
+ String[] p = splitTokens(loadPath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("GIF"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("gif"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("JPG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("jpg"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("TGA"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("tga"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("PNG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("png"))
+ fileOK = true;
+
+ println("File OK: " + fileOK);
+
+ if (fileOK) {
+ imgload = loadImage(loadPath);
+ fileLoaded = true;
+ // MainArraysetup();
+ ReInitiallizeArray = true;
+ }
+ else {
+ // Can't load file
+ ErrorDisplay = "ERROR: BAD FILE TYPE";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+} //End Load File
+
+
+
+void SAVE_PATH(float theValue) {
+ FileModeTSP = true;
+ SAVE_SVG(0);
+}
+
+
+
+void SAVE_STIPPLES(float theValue) {
+ FileModeTSP = false;
+ SAVE_SVG(0);
+}
+
+
+
+
+
+
+void SAVE_SVG(float theValue) {
+
+
+
+ if (pausemode != true) {
+ Pause(0.0);
+ ErrorDisplay = "Error: PAUSE before saving.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+ savePath = selectOutput("Output .svg file name:"); // Opens file chooser
+ if (savePath == null) {
+ // If a file was not selected
+ println("No output file was selected...");
+ ErrorDisplay = "ERROR: NO FILE NAME CHOSEN.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+
+ String[] p = splitTokens(savePath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("SVG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("svg"))
+ fileOK = true;
+
+ if (fileOK == false)
+ savePath = savePath + ".svg";
+
+
+ // If a file was selected, print path to folder
+ println("Save file: " + savePath);
+ SaveNow = 1;
+ showPath = true;
+
+ ErrorDisplay = "SAVING FILE...";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+}
+
+
+
+
+void QUIT(float theValue) {
+ exit();
+}
+
+
+void ORDER_ON_OFF(float theValue) {
+ if (showPath) {
+ showPath = false;
+ OrderOnOff.setCaptionLabel("Plotting path >> Hide");
+ }
+ else {
+ showPath = true;
+ OrderOnOff.setCaptionLabel("Plotting path >> Shown while paused");
+ }
+}
+
+void CELLS_ON_OFF(float theValue) {
+ if (showCells) {
+ showCells = false;
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+ }
+ else {
+ showCells = true;
+ CellOnOff.setCaptionLabel("Cells >> Show");
+ }
+}
+
+
+
+void IMG_ON_OFF(float theValue) {
+ if (showBG) {
+ showBG = false;
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+ }
+ else {
+ showBG = true;
+ ImgOnOff.setCaptionLabel("Image BG >> Show");
+ }
+}
+
+
+void INVERT_IMG(float theValue) {
+ if (invertImg) {
+ invertImg = false;
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+ }
+ else {
+ invertImg = true;
+ InvertOnOff.setCaptionLabel("White stipples, Black Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("Black Cutoff");
+ }
+
+ ReInitiallizeArray = true;
+ pausemode = false;
+}
+
+
+
+
+void Pause(float theValue) {
+ // Main particle array setup (to be repeated if necessary):
+
+ if (pausemode)
+ {
+ pausemode = false;
+ println("Resuming.");
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+ }
+ else
+ {
+ pausemode = true;
+ println("Paused. Press PAUSE again to resume.");
+ PauseButton.setCaptionLabel("Paused (calculating TSP path)");
+ }
+ RouteStep = 0;
+}
+
+
+boolean overRect(int x, int y, int width, int height)
+{
+ if (mouseX >= x && mouseX <= x+width &&
+ mouseY >= y && mouseY <= y+height) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+void Stipples(int inValue) {
+
+ if (maxParticles != (int) inValue) {
+ println("Update: Stipple Count -> " + inValue);
+ ReInitiallizeArray = true;
+ pausemode = false;
+ }
+}
+
+
+
+
+
+void Min_Dot_Size(float inValue) {
+ if (MinDotSize != inValue) {
+ println("Update: Min_Dot_Size -> "+inValue);
+ MinDotSize = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void Dot_Size_Range(float inValue) {
+ if (DotSizeFactor != inValue) {
+ println("Update: Dot Size Range -> "+inValue);
+ DotSizeFactor = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void White_Cutoff(float inValue) {
+ if (cutoff != inValue) {
+ println("Update: White_Cutoff -> "+inValue);
+ cutoff = inValue;
+ RouteStep = 0; // Reset TSP path
+ }
+}
+
+
+void DoBackgrounds() {
+ if (showBG)
+ image(img, 0, 0); // Show original (cropped and scaled, but not blurred!) image in background
+ else {
+
+ if (invertImg)
+ fill(0);
+ else
+ fill(255);
+
+ rect(0, 0, mainwidth, mainheight);
+ }
+}
+
+void OptimizePlotPath()
+{
+ int temp;
+ // Calculate and show "optimized" plotting path, beneath points.
+
+ StatusDisplay = "Optimizing plotting path";
+ /*
+ if (RouteStep % 100 == 0) {
+ println("RouteStep:" + RouteStep);
+ println("fps = " + frameRate );
+ }
+ */
+
+ Vec2D p1;
+
+
+ if (RouteStep == 0)
+ {
+
+ float cutoffScaled = 1 - cutoff;
+ // Begin process of optimizing plotting route, by flagging particles that will be shown.
+
+ particleRouteLength = 0;
+
+ boolean particleRouteTemp[] = new boolean[maxParticles];
+
+ for (int i = 0; i < maxParticles; ++i) {
+
+ particleRouteTemp[i] = false;
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+
+ if (v < cutoffScaled) {
+ particleRouteTemp[i] = true;
+ particleRouteLength++;
+ }
+ }
+
+ particleRoute = new int[particleRouteLength];
+ int tempCounter = 0;
+ for (int i = 0; i < maxParticles; ++i) {
+
+ if (particleRouteTemp[i])
+ {
+ particleRoute[tempCounter] = i;
+ tempCounter++;
+ }
+ }
+ // These are the ONLY points to be drawn in the tour.
+ }
+
+ if (RouteStep < (particleRouteLength - 2))
+ {
+
+ // Nearest neighbor ("Simple, Greedy") algorithm path optimization:
+
+ int StopPoint = RouteStep + 1000; // 1000 steps per frame displayed; you can edit this number!
+
+ if (StopPoint > (particleRouteLength - 1))
+ StopPoint = particleRouteLength - 1;
+
+ for (int i = RouteStep; i < StopPoint; ++i) {
+
+ p1 = particles[particleRoute[RouteStep]];
+ int ClosestParticle = 0;
+ float distMin = Float.MAX_VALUE;
+
+ for (int j = RouteStep + 1; j < (particleRouteLength - 1); ++j) {
+ Vec2D p2 = particles[particleRoute[j]];
+
+ float dx = p1.x - p2.x;
+ float dy = p1.y - p2.y;
+ float distance = (float) (dx*dx+dy*dy); // Only looking for closest; do not need sqrt factor!
+
+ if (distance < distMin) {
+ ClosestParticle = j;
+ distMin = distance;
+ }
+ }
+
+ temp = particleRoute[RouteStep + 1];
+ // p1 = particles[particleRoute[RouteStep + 1]];
+ particleRoute[RouteStep + 1] = particleRoute[ClosestParticle];
+ particleRoute[ClosestParticle] = temp;
+
+ if (RouteStep < (particleRouteLength - 1))
+ RouteStep++;
+ else
+ {
+ println("Now optimizing plot path" );
+ }
+ }
+ }
+ else
+ { // Initial routing is complete
+ // 2-opt heuristic optimization:
+ // Identify a pair of edges that would become shorter by reversing part of the tour.
+
+ for (int i = 0; i < 90000; ++i) { // 1000 tests per frame; you can edit this number.
+
+ int indexA = floor(random(particleRouteLength - 1));
+ int indexB = floor(random(particleRouteLength - 1));
+
+ if (Math.abs(indexA - indexB) < 2)
+ continue;
+
+ if (indexB < indexA)
+ { // swap A, B.
+ temp = indexB;
+ indexB = indexA;
+ indexA = temp;
+ }
+
+ Vec2D a0 = particles[particleRoute[indexA]];
+ Vec2D a1 = particles[particleRoute[indexA + 1]];
+ Vec2D b0 = particles[particleRoute[indexB]];
+ Vec2D b1 = particles[particleRoute[indexB + 1]];
+
+ // Original distance:
+ float dx = a0.x - a1.x;
+ float dy = a0.y - a1.y;
+ float distance = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = b0.x - b1.x;
+ dy = b0.y - b1.y;
+ distance += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ // Possible shorter distance?
+ dx = a0.x - b0.x;
+ dy = a0.y - b0.y;
+ float distance2 = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = a1.x - b1.x;
+ dy = a1.y - b1.y;
+ distance2 += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ if (distance2 < distance)
+ {
+ // Reverse tour between a1 and b0.
+
+ int indexhigh = indexB;
+ int indexlow = indexA + 1;
+
+ // println("Shorten!" + frameRate );
+
+ while (indexhigh > indexlow)
+ {
+
+ temp = particleRoute[indexlow];
+ particleRoute[indexlow] = particleRoute[indexhigh];
+ particleRoute[indexhigh] = temp;
+
+ indexhigh--;
+ indexlow++;
+ }
+ }
+ }
+ }
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+}
+
+
+
+
+
+
+
+void doPhysics()
+{ // Iterative relaxation via weighted Lloyd's algorithm.
+
+ int temp;
+ int CountTemp;
+
+ if (VoronoiCalculated == false)
+ { // Part I: Calculate voronoi cell diagram of the points.
+
+ StatusDisplay = "Calculating Voronoi diagram ";
+
+ // float millisBaseline = millis(); // Baseline for timing studies
+ // println("Baseline. Time = " + (millis() - millisBaseline) );
+
+
+ if (vorPointsAdded == 0)
+ voronoi = new Voronoi(); // Erase mesh
+
+ temp = vorPointsAdded + 200; // This line: VoronoiPointsPerPass (Feel free to edit this number.)
+ if (temp > maxParticles)
+ temp = maxParticles;
+
+ for (int i = vorPointsAdded; i < temp; ++i) {
+
+
+ // Optional, for diagnostics:::
+ // println("particles[i].x, particles[i].y " + particles[i].x + ", " + particles[i].y );
+
+
+ //
+
+
+ voronoi.addPoint(new Vec2D(particles[i].x, particles[i].y ));
+ vorPointsAdded++;
+ }
+
+ if (vorPointsAdded >= maxParticles)
+ {
+
+ // println("Points added. Time = " + (millis() - millisBaseline) );
+
+ cellsTotal = (voronoi.getRegions().size());
+ vorPointsAdded = 0;
+ cellsCalculated = 0;
+ cellsCalculatedLast = 0;
+
+ RegionList = new Polygon2D[cellsTotal];
+
+ int i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ RegionList[i++] = poly; // Build array of polygons
+ }
+ VoronoiCalculated = true;
+ }
+ }
+ else
+ { // Part II: Calculate weighted centroids of cells.
+ // float millisBaseline = millis();
+ // println("fps = " + frameRate );
+
+ StatusDisplay = "Calculating weighted centroids";
+
+
+ temp = cellsCalculated + 100; // This line: CentroidsPerPass (Feel free to edit this number.)
+ // Higher values give slightly faster computation, but a less responsive GUI.
+
+
+ if (temp > cellsTotal)
+ {
+ temp = cellsTotal;
+ }
+
+ for (int i=cellsCalculated; i< temp; i++) {
+
+ float xMax = 0;
+ float xMin = mainwidth;
+ float yMax = 0;
+ float yMin = mainheight;
+ float xt, yt;
+
+ Polygon2D region = clip.clipPolygon(RegionList[i]);
+
+
+ for (Vec2D v : region.vertices) {
+
+ xt = v.x;
+ yt = v.y;
+
+ if (xt < xMin)
+ xMin = xt;
+ if (xt > xMax)
+ xMax = xt;
+ if (yt < yMin)
+ yMin = yt;
+ if (yt > yMax)
+ yMax = yt;
+ }
+
+ float xDiff = xMax - xMin;
+ float yDiff = yMax - yMin;
+ float maxSize = max(xDiff, yDiff);
+ float minSize = min(xDiff, yDiff);
+
+ float scaleFactor = 1.0;
+
+ // Maximum voronoi cell extent should be between
+ // cellBuffer/2 and cellBuffer in size.
+
+ while (maxSize > cellBuffer)
+ {
+ scaleFactor *= 0.5;
+ maxSize *= 0.5;
+ }
+
+ while (maxSize < (cellBuffer/2))
+ {
+ scaleFactor *= 2;
+ maxSize *= 2;
+ }
+
+ if ((minSize * scaleFactor) > (cellBuffer/2))
+ { // Special correction for objects of near-unity (square-like) aspect ratio,
+ // which have larger area *and* where it is less essential to find the exact centroid:
+ scaleFactor *= 0.5;
+ }
+
+ float StepSize = (1/scaleFactor);
+
+ float xSum = 0;
+ float ySum = 0;
+ float dSum = 0;
+ float PicDensity = 1.0;
+
+
+ if (invertImg)
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 0.001 + (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+ else
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 255.001 - (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+
+ if (dSum > 0)
+ {
+ xSum /= dSum;
+ ySum /= dSum;
+ }
+
+ Vec2D centr;
+
+
+ float xTemp = (xSum);
+ float yTemp = (ySum);
+
+
+ if ((xTemp <= lowBorderX) || (xTemp >= hiBorderX) || (yTemp <= lowBorderY) || (yTemp >= hiBorderY)) {
+ // If new centroid is computed to be outside the visible region, use the geometric centroid instead.
+ // This will help to prevent runaway points due to numerical artifacts.
+ centr = region.getCentroid();
+ xTemp = centr.x;
+ yTemp = centr.y;
+
+ // Enforce sides, if absolutely necessary: (Failure to do so *will* cause a crash, eventually.)
+
+ if (xTemp <= lowBorderX)
+ xTemp = lowBorderX + 1;
+ if (xTemp >= hiBorderX)
+ xTemp = hiBorderX - 1;
+ if (yTemp <= lowBorderY)
+ yTemp = lowBorderY + 1;
+ if (yTemp >= hiBorderY)
+ yTemp = hiBorderY - 1;
+ }
+
+ particles[i].x = xTemp;
+ particles[i].y = yTemp;
+
+ cellsCalculated++;
+ }
+
+
+ // println("cellsCalculated = " + cellsCalculated );
+ // println("cellsTotal = " + cellsTotal );
+
+ if (cellsCalculated >= cellsTotal)
+ {
+ VoronoiCalculated = false;
+ Generation++;
+ println("Generation = " + Generation );
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+ }
+ }
+}
+
+
+void draw()
+{
+
+ int i = 0;
+ int temp;
+ float dotScale = (MaxDotSize - MinDotSize);
+ float cutoffScaled = 1 - cutoff;
+
+ if (ReInitiallizeArray) {
+ maxParticles = (int) cp5.controller("Stipples").value(); // Only change this here!
+
+ MainArraysetup();
+ ReInitiallizeArray = false;
+ }
+
+ if (pausemode && (VoronoiCalculated == false))
+ OptimizePlotPath();
+ else
+ doPhysics();
+
+
+ if (pausemode)
+ {
+
+ DoBackgrounds();
+
+ // Draw paths:
+
+ if ( showPath ) {
+
+ stroke(128, 128, 255); // Stroke color (blue)
+ strokeWeight (1);
+
+ for ( i = 0; i < (particleRouteLength - 1); ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+ Vec2D p2 = particles[particleRoute[i + 1]];
+
+ line(p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+ // Only show "routed" particles-- those above the white cutoff.
+
+ Vec2D p1 = particles[particleRoute[i]];
+ int px = (int) p1.x;
+ int py = (int) p1.y;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ else
+ { // NOT in pause mode. i.e., just displaying stipples.
+ if (cellsCalculated == 0) {
+
+ DoBackgrounds();
+
+ if (Generation == 0)
+ {
+ TempShowCells = true;
+ }
+
+ if (showCells || TempShowCells) { // Draw voronoi cells, over background.
+ strokeWeight(1);
+ noFill();
+
+
+ if (invertImg && (showBG == false)) // TODO -- if invertImg AND NOT background
+ stroke(100);
+ else
+ stroke(200);
+
+ // stroke(200);
+
+ i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ //RegionList[i++] = poly;
+ gfx.polygon2D(clip.clipPolygon(poly));
+ }
+ }
+
+ if (showCells) {
+ // Show "before and after" centroids, when polygons are shown.
+
+ strokeWeight (MinDotSize); // Normal w/ Min & Max dot size
+ for ( i = 0; i < maxParticles; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ //Uncomment the following four lines, if you wish to display the "before" dots at weighted sizes.
+ //float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+ //if (invertImg)
+ //v = 1 - v;
+ //strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+ }
+ else {
+ // Stipple calculation is still underway
+
+ if (TempShowCells)
+ {
+ DoBackgrounds();
+ TempShowCells = false;
+ }
+
+
+ // stroke(0); // Stroke color
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = cellsCalculatedLast; i < cellsCalculated; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ if (v < cutoffScaled) {
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+
+ cellsCalculatedLast = cellsCalculated;
+ }
+ }
+
+ noStroke();
+ fill(100); // Background fill color
+ rect(0, mainheight, mainwidth, height); // Control area fill
+
+ // Underlay for hyperlink:
+ if (overRect(TextColumnStart - 10, mainheight + 35, 205, 20) )
+ {
+ fill(150);
+ rect(TextColumnStart - 10, mainheight + 35, 205, 20);
+ }
+
+ fill(255); // Text color
+
+ text("StippleGen 2 (v. 2.02)", TextColumnStart, mainheight + 15);
+ text("by Evil Mad Scientist Laboratories", TextColumnStart, mainheight + 30);
+ text("www.evilmadscientist.com/go/stipple2", TextColumnStart, mainheight + 50);
+
+ text("Generations completed: " + Generation, TextColumnStart, mainheight + 85);
+ text("Time/Frame: " + frameTime + " s", TextColumnStart, mainheight + 100);
+
+
+ if (ErrorDisp)
+ {
+ fill(255, 0, 0); // Text color
+ text(ErrorDisplay, TextColumnStart, mainheight + 70);
+ if ((millis() - ErrorTime) > 8000)
+ ErrorDisp = false;
+ }
+ else
+ text("Status: " + StatusDisplay, TextColumnStart, mainheight + 70);
+
+
+
+ if (SaveNow > 0) {
+
+ StatusDisplay = "Saving SVG File";
+ SaveNow = 0;
+
+ FileOutput = loadStrings("header.txt");
+
+ String rowTemp;
+
+ float SVGscale = (800.0 / (float) mainheight);
+ int xOffset = (int) (1600 - (SVGscale * mainwidth / 2));
+ int yOffset = (int) (400 - (SVGscale * mainheight / 2));
+
+
+ if (FileModeTSP)
+ { // Plot the PATH between the points only.
+
+ println("Save TSP File (SVG)");
+
+ // Path header::
+ rowTemp = ""); // End path description
+ }
+ else {
+ println("Save Stipple File (SVG)");
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+
+ int px = floor(p1.x);
+ int py = floor(p1.y);
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ float dotrad = (MaxDotSize - v * dotScale)/2;
+
+ float xTemp = SVGscale*p1.x + xOffset;
+ float yTemp = SVGscale*p1.y + yOffset;
+
+ rowTemp = "";
+
+ // Typ:
+
+ FileOutput = append(FileOutput, rowTemp);
+ }
+ }
+
+
+
+ // SVG footer:
+ FileOutput = append(FileOutput, "");
+ saveStrings(savePath, FileOutput);
+ FileModeTSP = false; // reset for next time
+
+ if (FileModeTSP)
+ ErrorDisplay = "TSP Path .SVG file Saved";
+ else
+ ErrorDisplay = "Stipple .SVG file saved ";
+
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+}
+
+
+
+void mousePressed() {
+
+ // rect(TextColumnStart, mainheight, 200, 75);
+
+ if (overRect(TextColumnStart - 15, mainheight + 35, 205, 20) )
+ link("http://www.evilmadscientist.com/go/stipple2");
+}
+
+
+
+
+void keyPressed() {
+ if (key == 'x')
+ { // If this program doesn't run slowly enough for you,
+ // simply press the 'x' key on your keyboard. :)
+ cp5.controller("Stipples").setMax(50000.0);
+ }
+}
+
diff --git a/StippleGen_2.02/StippleGen_2/application.linux/StippleGen_2 b/StippleGen_2.02/StippleGen_2/application.linux/StippleGen_2
new file mode 100644
index 0000000..fb7e266
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.linux/StippleGen_2
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+APPDIR=$(dirname "$0")
+java -Djava.library.path="$APPDIR" -cp "$APPDIR/lib/StippleGen_2.jar:$APPDIR/lib/core.jar:$APPDIR/lib/controlP5.jar:$APPDIR/lib/toxiclibscore.jar:$APPDIR/lib/toxiclibs_p5.jar" StippleGen_2
diff --git a/StippleGen_2.02/StippleGen_2/application.linux/lib/StippleGen_2.jar b/StippleGen_2.02/StippleGen_2/application.linux/lib/StippleGen_2.jar
new file mode 100644
index 0000000..80f52e3
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.linux/lib/StippleGen_2.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.linux/lib/controlP5.jar b/StippleGen_2.02/StippleGen_2/application.linux/lib/controlP5.jar
new file mode 100644
index 0000000..a9ac250
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.linux/lib/controlP5.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.linux/lib/core.jar b/StippleGen_2.02/StippleGen_2/application.linux/lib/core.jar
new file mode 100644
index 0000000..6cdcb96
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.linux/lib/core.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.linux/lib/toxiclibs_p5.jar b/StippleGen_2.02/StippleGen_2/application.linux/lib/toxiclibs_p5.jar
new file mode 100644
index 0000000..bc4d432
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.linux/lib/toxiclibs_p5.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.linux/lib/toxiclibscore.jar b/StippleGen_2.02/StippleGen_2/application.linux/lib/toxiclibscore.jar
new file mode 100644
index 0000000..24d5405
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.linux/lib/toxiclibscore.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.linux/source/StippleGen_2.pde b/StippleGen_2.02/StippleGen_2/application.linux/source/StippleGen_2.pde
new file mode 100644
index 0000000..40c0771
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.linux/source/StippleGen_2.pde
@@ -0,0 +1,1373 @@
+/**
+
+ StippleGen_2
+
+ 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
+
+ *******************************************************************************
+
+ Change Log:
+
+ v 2.02
+ * Force files to end in .svg
+ * Fix bug that gave wrong size to stipple files saved white stipples on black background
+
+ v 2.01:
+ * Improved handling of Save process, to prevent accidental "not saving" by users.
+
+ v 2.0:
+ * Add tone reversal option (white on black / black on white)
+ * Reduce vertical extent of GUI, to reduce likelihood of cropping on small screens
+ * Speling corections
+ * Fixed a bug that caused unintended cropping of long, wide images
+ * Reorganized GUI controls
+ * Fail less disgracefully when a bad image type is selected.
+
+ *******************************************************************************
+
+
+
+ Program is based on the Toxic Libs Library ( http://toxiclibs.org/ )
+ & example code:
+ http://forum.processing.org/topic/toxiclib-voronoi-example-sketch
+
+
+ Additional inspiration:
+ Stipple Cam from Jim Bumgardner
+ http://joyofprocessing.com/blog/2011/11/stipple-cam/
+
+ and
+
+ MeshLibDemo.pde - Demo of Lee Byron's Mesh library, by
+ Marius Watz - http://workshop.evolutionzone.com/
+
+
+ Requires ControlP5 library and Toxic Libs library:
+ http://www.sojamo.de/libraries/controlP5/
+ http://hg.postspectacular.com/toxiclibs/downloads
+
+
+ */
+
+
+/*
+ *
+ * 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
+ */
+
+
+// You need the controlP5 library from http://www.sojamo.de/libraries/controlP5/
+import controlP5.*;
+
+//You need the Toxic Libs library: http://hg.postspectacular.com/toxiclibs/downloads
+
+import toxi.geom.*;
+import toxi.geom.mesh2d.*;
+import toxi.util.datatypes.*;
+import toxi.processing.*;
+
+// helper class for rendering
+ToxiclibsSupport gfx;
+
+import javax.swing.UIManager;
+import javax.swing.JFileChooser;
+
+
+
+
+// Feel free to play with these three default settings:
+int maxParticles = 2000; // Max value is normally 10000. Press 'x' key to allow 50000 stipples. (SLOW)
+float MinDotSize = 1.75; //2;
+float DotSizeFactor = 4; //5;
+float cutoff = 0; // White cutoff value
+
+
+int cellBuffer = 100; //Scale each cell to fit in a cellBuffer-sized square window for computing the centroid.
+
+
+// Display window and GUI area sizes:
+int mainwidth;
+int mainheight;
+int borderWidth;
+int ctrlheight;
+int TextColumnStart;
+
+
+
+float lowBorderX;
+float hiBorderX;
+float lowBorderY;
+float hiBorderY;
+
+
+
+float MaxDotSize;
+boolean ReInitiallizeArray;
+boolean pausemode;
+boolean fileLoaded;
+int SaveNow;
+String savePath;
+String[] FileOutput;
+
+
+
+
+String StatusDisplay = "Initializing, please wait. :)";
+float millisLastFrame = 0;
+float frameTime = 0;
+
+String ErrorDisplay = "";
+float ErrorTime;
+Boolean ErrorDisp = false;
+
+
+int Generation;
+int particleRouteLength;
+int RouteStep;
+
+boolean showBG;
+boolean showPath;
+boolean showCells;
+boolean invertImg;
+boolean TempShowCells;
+boolean FileModeTSP;
+
+int vorPointsAdded;
+boolean VoronoiCalculated;
+
+// Toxic libs library setup:
+Voronoi voronoi;
+Polygon2D RegionList[];
+
+PolygonClipper2D clip; // polygon clipper
+
+int cellsTotal, cellsCalculated, cellsCalculatedLast;
+
+
+// ControlP5 GUI library variables setup
+Textlabel ProgName;
+Button OrderOnOff, ImgOnOff, CellOnOff, InvertOnOff, PauseButton;
+ControlP5 cp5;
+
+
+PImage img, imgload, imgblur;
+
+Vec2D[] particles;
+int[] particleRoute;
+
+
+
+void LoadImageAndScale() {
+
+ int tempx = 0;
+ int tempy = 0;
+
+ img = createImage(mainwidth, mainheight, RGB);
+ imgblur = createImage(mainwidth, mainheight, RGB);
+
+ img.loadPixels();
+
+ if (invertImg)
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(0);
+ }
+ else
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(255);
+ }
+
+ img.updatePixels();
+
+ if ( fileLoaded == false) {
+ // Load a demo image, at least until we have a "real" image to work with.
+
+ imgload = loadImage("grace.jpg"); // Load demo image
+ // Image source: http://commons.wikimedia.org/wiki/File:Kelly,_Grace_(Rear_Window).jpg
+ }
+
+ if ((imgload.width > mainwidth) || (imgload.height > mainheight)) {
+
+ if (((float) imgload.width / (float)imgload.height) > ((float) mainwidth / (float) mainheight))
+ {
+ imgload.resize(mainwidth, 0);
+ }
+ else
+ {
+ imgload.resize(0, mainheight);
+ }
+ }
+
+ if (imgload.height < (mainheight - 2) ) {
+ tempy = (int) (( mainheight - imgload.height ) / 2) ;
+ }
+ if (imgload.width < (mainwidth - 2)) {
+ tempx = (int) (( mainwidth - imgload.width ) / 2) ;
+ }
+
+ img.copy(imgload, 0, 0, imgload.width, imgload.height, tempx, tempy, imgload.width, imgload.height);
+ // For background image!
+
+
+ /*
+ // Optional gamma correction for background image.
+ img.loadPixels();
+
+ float tempFloat;
+ float GammaValue = 1.0; // Normally in the range 0.25 - 4.0
+
+ for (int i = 0; i < img.pixels.length; i++) {
+ tempFloat = brightness(img.pixels[i])/255;
+ img.pixels[i] = color(floor(255 * pow(tempFloat,GammaValue)));
+ }
+ img.updatePixels();
+ */
+
+
+ imgblur.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
+ // This is a duplicate of the background image, that we will apply a blur to,
+ // to reduce "high frequency" noise artifacts.
+
+ imgblur.filter(BLUR, 1); // Low-level blur filter to elminate pixel-to-pixel noise artifacts.
+ imgblur.loadPixels();
+}
+
+
+void MainArraysetup() {
+ // Main particle array initialization (to be called whenever necessary):
+
+ LoadImageAndScale();
+
+ // image(img, 0, 0); // SHOW BG IMG
+
+ particles = new Vec2D[maxParticles];
+
+
+ // Fill array by "rejection sampling"
+ int i = 0;
+ while (i < maxParticles)
+ {
+
+ float fx = lowBorderX + random(hiBorderX - lowBorderX);
+ float fy = lowBorderY + random(hiBorderY - lowBorderY);
+
+ float p = brightness(imgblur.pixels[ floor(fy)*imgblur.width + floor(fx) ])/255;
+ // OK to use simple floor_ rounding here, because this is a one-time operation,
+ // creating the initial distribution that will be iterated.
+
+ if (invertImg)
+ {
+ p = 1 - p;
+ }
+
+ if (random(1) >= p ) {
+ Vec2D p1 = new Vec2D(fx, fy);
+ particles[i] = p1;
+ i++;
+ }
+ }
+
+ particleRouteLength = 0;
+ Generation = 0;
+ millisLastFrame = millis();
+ RouteStep = 0;
+ VoronoiCalculated = false;
+ cellsCalculated = 0;
+ vorPointsAdded = 0;
+ voronoi = new Voronoi(); // Erase mesh
+ TempShowCells = true;
+ FileModeTSP = false;
+}
+
+void setup()
+{
+
+ borderWidth = 6;
+
+ mainwidth = 800;
+ mainheight = 600;
+ ctrlheight = 110;
+
+ size(mainwidth, mainheight + ctrlheight, JAVA2D);
+
+ gfx = new ToxiclibsSupport(this);
+
+
+ lowBorderX = borderWidth; //mainwidth*0.01;
+ hiBorderX = mainwidth - borderWidth; //mainwidth*0.98;
+ lowBorderY = borderWidth; // mainheight*0.01;
+ hiBorderY = mainheight - borderWidth; //mainheight*0.98;
+
+ int innerWidth = mainwidth - 2 * borderWidth;
+ int innerHeight = mainheight - 2 * borderWidth;
+
+ clip=new SutherlandHodgemanClipper(new Rect(lowBorderX, lowBorderY, innerWidth, innerHeight));
+
+ MainArraysetup(); // Main particle array setup
+
+ frameRate(24);
+
+ smooth();
+ noStroke();
+ fill(153); // Background fill color, for control section
+
+ textFont(createFont("SansSerif", 10));
+
+
+ cp5 = new ControlP5(this);
+
+ int leftcolumwidth = 225;
+
+ int GUItop = mainheight + 15;
+ int GUI2ndRow = 4; // Spacing for firt row after group heading
+ int GuiRowSpacing = 14; // Spacing for subsequent rows
+ int GUIFudge = mainheight + 19; // I wish that we didn't need ONE MORE of these stupid spacings.
+
+
+ ControlGroup l3 = cp5.addGroup("Primary controls (Changing will restart)", 10, GUItop, 225);
+
+ cp5.addSlider("Stipples", 10, 10000, maxParticles, 10, GUI2ndRow, 150, 10).setGroup(l3);
+
+ InvertOnOff = cp5.addButton("INVERT_IMG", 10, 10, GUI2ndRow + GuiRowSpacing, 190, 10).setGroup(l3);
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+
+
+ Button LoadButton = cp5.addButton("LOAD_FILE", 10, 10, GUIFudge + 3*GuiRowSpacing, 175, 10);
+ LoadButton.setCaptionLabel("LOAD IMAGE FILE (.PNG, .JPG, or .GIF)");
+
+ cp5.addButton("QUIT", 10, 205, GUIFudge + 3*GuiRowSpacing, 30, 10);
+
+ cp5.addButton("SAVE_STIPPLES", 10, 25, GUIFudge + 4*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_STIPPLES").setCaptionLabel("Save Stipple File (.SVG format)");
+
+ cp5.addButton("SAVE_PATH", 10, 25, GUIFudge + 5*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_PATH").setCaptionLabel("Save \"TSP\" Path (.SVG format)");
+
+
+ ControlGroup l5 = cp5.addGroup("Display Options - Updated on next generation", leftcolumwidth+50, GUItop, 225);
+
+ cp5.addSlider("Min_Dot_Size", .5, 8, 2, 10, 4, 140, 10).setGroup(l5);
+ cp5.controller("Min_Dot_Size").setValue(MinDotSize);
+ cp5.controller("Min_Dot_Size").setCaptionLabel("Min. Dot Size");
+
+ cp5.addSlider("Dot_Size_Range", 0, 20, 5, 10, 18, 140, 10).setGroup(l5);
+ cp5.controller("Dot_Size_Range").setValue(DotSizeFactor);
+ cp5.controller("Dot_Size_Range").setCaptionLabel("Dot Size Range");
+
+ cp5.addSlider("White_Cutoff", 0, 1, 0, 10, 32, 140, 10).setGroup(l5);
+ cp5.controller("White_Cutoff").setValue(cutoff);
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+
+
+ ImgOnOff = cp5.addButton("IMG_ON_OFF", 10, 10, 46, 90, 10);
+ ImgOnOff.setGroup(l5);
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+
+ CellOnOff = cp5.addButton("CELLS_ON_OFF", 10, 110, 46, 90, 10);
+ CellOnOff.setGroup(l5);
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+
+ PauseButton = cp5.addButton("Pause", 10, 10, 60, 190, 10);
+ PauseButton.setGroup(l5);
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+
+ OrderOnOff = cp5.addButton("ORDER_ON_OFF", 10, 10, 74, 190, 10);
+ OrderOnOff.setGroup(l5);
+ OrderOnOff.setCaptionLabel("Plotting path >> shown while paused");
+
+
+
+
+
+ TextColumnStart = 2 * leftcolumwidth + 100;
+
+ MaxDotSize = MinDotSize * (1 + DotSizeFactor);
+
+ ReInitiallizeArray = false;
+ pausemode = false;
+ showBG = false;
+ invertImg = false;
+ showPath = true;
+ showCells = false;
+ fileLoaded = false;
+ SaveNow = 0;
+}
+
+
+void LOAD_FILE(float theValue) {
+ println(":::LOAD JPG, GIF or PNG FILE:::");
+
+ String loadPath = selectInput(); // Opens file chooser
+ if (loadPath == null) {
+ // If a file was not selected
+ println("No file was selected...");
+ }
+ else {
+ // If a file was selected, print path to file
+ println("Loaded file: " + loadPath);
+
+
+ String[] p = splitTokens(loadPath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("GIF"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("gif"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("JPG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("jpg"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("TGA"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("tga"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("PNG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("png"))
+ fileOK = true;
+
+ println("File OK: " + fileOK);
+
+ if (fileOK) {
+ imgload = loadImage(loadPath);
+ fileLoaded = true;
+ // MainArraysetup();
+ ReInitiallizeArray = true;
+ }
+ else {
+ // Can't load file
+ ErrorDisplay = "ERROR: BAD FILE TYPE";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+} //End Load File
+
+
+
+void SAVE_PATH(float theValue) {
+ FileModeTSP = true;
+ SAVE_SVG(0);
+}
+
+
+
+void SAVE_STIPPLES(float theValue) {
+ FileModeTSP = false;
+ SAVE_SVG(0);
+}
+
+
+
+
+
+
+void SAVE_SVG(float theValue) {
+
+
+
+ if (pausemode != true) {
+ Pause(0.0);
+ ErrorDisplay = "Error: PAUSE before saving.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+ savePath = selectOutput("Output .svg file name:"); // Opens file chooser
+ if (savePath == null) {
+ // If a file was not selected
+ println("No output file was selected...");
+ ErrorDisplay = "ERROR: NO FILE NAME CHOSEN.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+
+ String[] p = splitTokens(savePath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("SVG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("svg"))
+ fileOK = true;
+
+ if (fileOK == false)
+ savePath = savePath + ".svg";
+
+
+ // If a file was selected, print path to folder
+ println("Save file: " + savePath);
+ SaveNow = 1;
+ showPath = true;
+
+ ErrorDisplay = "SAVING FILE...";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+}
+
+
+
+
+void QUIT(float theValue) {
+ exit();
+}
+
+
+void ORDER_ON_OFF(float theValue) {
+ if (showPath) {
+ showPath = false;
+ OrderOnOff.setCaptionLabel("Plotting path >> Hide");
+ }
+ else {
+ showPath = true;
+ OrderOnOff.setCaptionLabel("Plotting path >> Shown while paused");
+ }
+}
+
+void CELLS_ON_OFF(float theValue) {
+ if (showCells) {
+ showCells = false;
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+ }
+ else {
+ showCells = true;
+ CellOnOff.setCaptionLabel("Cells >> Show");
+ }
+}
+
+
+
+void IMG_ON_OFF(float theValue) {
+ if (showBG) {
+ showBG = false;
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+ }
+ else {
+ showBG = true;
+ ImgOnOff.setCaptionLabel("Image BG >> Show");
+ }
+}
+
+
+void INVERT_IMG(float theValue) {
+ if (invertImg) {
+ invertImg = false;
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+ }
+ else {
+ invertImg = true;
+ InvertOnOff.setCaptionLabel("White stipples, Black Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("Black Cutoff");
+ }
+
+ ReInitiallizeArray = true;
+ pausemode = false;
+}
+
+
+
+
+void Pause(float theValue) {
+ // Main particle array setup (to be repeated if necessary):
+
+ if (pausemode)
+ {
+ pausemode = false;
+ println("Resuming.");
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+ }
+ else
+ {
+ pausemode = true;
+ println("Paused. Press PAUSE again to resume.");
+ PauseButton.setCaptionLabel("Paused (calculating TSP path)");
+ }
+ RouteStep = 0;
+}
+
+
+boolean overRect(int x, int y, int width, int height)
+{
+ if (mouseX >= x && mouseX <= x+width &&
+ mouseY >= y && mouseY <= y+height) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+void Stipples(int inValue) {
+
+ if (maxParticles != (int) inValue) {
+ println("Update: Stipple Count -> " + inValue);
+ ReInitiallizeArray = true;
+ pausemode = false;
+ }
+}
+
+
+
+
+
+void Min_Dot_Size(float inValue) {
+ if (MinDotSize != inValue) {
+ println("Update: Min_Dot_Size -> "+inValue);
+ MinDotSize = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void Dot_Size_Range(float inValue) {
+ if (DotSizeFactor != inValue) {
+ println("Update: Dot Size Range -> "+inValue);
+ DotSizeFactor = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void White_Cutoff(float inValue) {
+ if (cutoff != inValue) {
+ println("Update: White_Cutoff -> "+inValue);
+ cutoff = inValue;
+ RouteStep = 0; // Reset TSP path
+ }
+}
+
+
+void DoBackgrounds() {
+ if (showBG)
+ image(img, 0, 0); // Show original (cropped and scaled, but not blurred!) image in background
+ else {
+
+ if (invertImg)
+ fill(0);
+ else
+ fill(255);
+
+ rect(0, 0, mainwidth, mainheight);
+ }
+}
+
+void OptimizePlotPath()
+{
+ int temp;
+ // Calculate and show "optimized" plotting path, beneath points.
+
+ StatusDisplay = "Optimizing plotting path";
+ /*
+ if (RouteStep % 100 == 0) {
+ println("RouteStep:" + RouteStep);
+ println("fps = " + frameRate );
+ }
+ */
+
+ Vec2D p1;
+
+
+ if (RouteStep == 0)
+ {
+
+ float cutoffScaled = 1 - cutoff;
+ // Begin process of optimizing plotting route, by flagging particles that will be shown.
+
+ particleRouteLength = 0;
+
+ boolean particleRouteTemp[] = new boolean[maxParticles];
+
+ for (int i = 0; i < maxParticles; ++i) {
+
+ particleRouteTemp[i] = false;
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+
+ if (v < cutoffScaled) {
+ particleRouteTemp[i] = true;
+ particleRouteLength++;
+ }
+ }
+
+ particleRoute = new int[particleRouteLength];
+ int tempCounter = 0;
+ for (int i = 0; i < maxParticles; ++i) {
+
+ if (particleRouteTemp[i])
+ {
+ particleRoute[tempCounter] = i;
+ tempCounter++;
+ }
+ }
+ // These are the ONLY points to be drawn in the tour.
+ }
+
+ if (RouteStep < (particleRouteLength - 2))
+ {
+
+ // Nearest neighbor ("Simple, Greedy") algorithm path optimization:
+
+ int StopPoint = RouteStep + 1000; // 1000 steps per frame displayed; you can edit this number!
+
+ if (StopPoint > (particleRouteLength - 1))
+ StopPoint = particleRouteLength - 1;
+
+ for (int i = RouteStep; i < StopPoint; ++i) {
+
+ p1 = particles[particleRoute[RouteStep]];
+ int ClosestParticle = 0;
+ float distMin = Float.MAX_VALUE;
+
+ for (int j = RouteStep + 1; j < (particleRouteLength - 1); ++j) {
+ Vec2D p2 = particles[particleRoute[j]];
+
+ float dx = p1.x - p2.x;
+ float dy = p1.y - p2.y;
+ float distance = (float) (dx*dx+dy*dy); // Only looking for closest; do not need sqrt factor!
+
+ if (distance < distMin) {
+ ClosestParticle = j;
+ distMin = distance;
+ }
+ }
+
+ temp = particleRoute[RouteStep + 1];
+ // p1 = particles[particleRoute[RouteStep + 1]];
+ particleRoute[RouteStep + 1] = particleRoute[ClosestParticle];
+ particleRoute[ClosestParticle] = temp;
+
+ if (RouteStep < (particleRouteLength - 1))
+ RouteStep++;
+ else
+ {
+ println("Now optimizing plot path" );
+ }
+ }
+ }
+ else
+ { // Initial routing is complete
+ // 2-opt heuristic optimization:
+ // Identify a pair of edges that would become shorter by reversing part of the tour.
+
+ for (int i = 0; i < 90000; ++i) { // 1000 tests per frame; you can edit this number.
+
+ int indexA = floor(random(particleRouteLength - 1));
+ int indexB = floor(random(particleRouteLength - 1));
+
+ if (Math.abs(indexA - indexB) < 2)
+ continue;
+
+ if (indexB < indexA)
+ { // swap A, B.
+ temp = indexB;
+ indexB = indexA;
+ indexA = temp;
+ }
+
+ Vec2D a0 = particles[particleRoute[indexA]];
+ Vec2D a1 = particles[particleRoute[indexA + 1]];
+ Vec2D b0 = particles[particleRoute[indexB]];
+ Vec2D b1 = particles[particleRoute[indexB + 1]];
+
+ // Original distance:
+ float dx = a0.x - a1.x;
+ float dy = a0.y - a1.y;
+ float distance = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = b0.x - b1.x;
+ dy = b0.y - b1.y;
+ distance += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ // Possible shorter distance?
+ dx = a0.x - b0.x;
+ dy = a0.y - b0.y;
+ float distance2 = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = a1.x - b1.x;
+ dy = a1.y - b1.y;
+ distance2 += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ if (distance2 < distance)
+ {
+ // Reverse tour between a1 and b0.
+
+ int indexhigh = indexB;
+ int indexlow = indexA + 1;
+
+ // println("Shorten!" + frameRate );
+
+ while (indexhigh > indexlow)
+ {
+
+ temp = particleRoute[indexlow];
+ particleRoute[indexlow] = particleRoute[indexhigh];
+ particleRoute[indexhigh] = temp;
+
+ indexhigh--;
+ indexlow++;
+ }
+ }
+ }
+ }
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+}
+
+
+
+
+
+
+
+void doPhysics()
+{ // Iterative relaxation via weighted Lloyd's algorithm.
+
+ int temp;
+ int CountTemp;
+
+ if (VoronoiCalculated == false)
+ { // Part I: Calculate voronoi cell diagram of the points.
+
+ StatusDisplay = "Calculating Voronoi diagram ";
+
+ // float millisBaseline = millis(); // Baseline for timing studies
+ // println("Baseline. Time = " + (millis() - millisBaseline) );
+
+
+ if (vorPointsAdded == 0)
+ voronoi = new Voronoi(); // Erase mesh
+
+ temp = vorPointsAdded + 200; // This line: VoronoiPointsPerPass (Feel free to edit this number.)
+ if (temp > maxParticles)
+ temp = maxParticles;
+
+ for (int i = vorPointsAdded; i < temp; ++i) {
+
+
+ // Optional, for diagnostics:::
+ // println("particles[i].x, particles[i].y " + particles[i].x + ", " + particles[i].y );
+
+
+ //
+
+
+ voronoi.addPoint(new Vec2D(particles[i].x, particles[i].y ));
+ vorPointsAdded++;
+ }
+
+ if (vorPointsAdded >= maxParticles)
+ {
+
+ // println("Points added. Time = " + (millis() - millisBaseline) );
+
+ cellsTotal = (voronoi.getRegions().size());
+ vorPointsAdded = 0;
+ cellsCalculated = 0;
+ cellsCalculatedLast = 0;
+
+ RegionList = new Polygon2D[cellsTotal];
+
+ int i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ RegionList[i++] = poly; // Build array of polygons
+ }
+ VoronoiCalculated = true;
+ }
+ }
+ else
+ { // Part II: Calculate weighted centroids of cells.
+ // float millisBaseline = millis();
+ // println("fps = " + frameRate );
+
+ StatusDisplay = "Calculating weighted centroids";
+
+
+ temp = cellsCalculated + 100; // This line: CentroidsPerPass (Feel free to edit this number.)
+ // Higher values give slightly faster computation, but a less responsive GUI.
+
+
+ if (temp > cellsTotal)
+ {
+ temp = cellsTotal;
+ }
+
+ for (int i=cellsCalculated; i< temp; i++) {
+
+ float xMax = 0;
+ float xMin = mainwidth;
+ float yMax = 0;
+ float yMin = mainheight;
+ float xt, yt;
+
+ Polygon2D region = clip.clipPolygon(RegionList[i]);
+
+
+ for (Vec2D v : region.vertices) {
+
+ xt = v.x;
+ yt = v.y;
+
+ if (xt < xMin)
+ xMin = xt;
+ if (xt > xMax)
+ xMax = xt;
+ if (yt < yMin)
+ yMin = yt;
+ if (yt > yMax)
+ yMax = yt;
+ }
+
+ float xDiff = xMax - xMin;
+ float yDiff = yMax - yMin;
+ float maxSize = max(xDiff, yDiff);
+ float minSize = min(xDiff, yDiff);
+
+ float scaleFactor = 1.0;
+
+ // Maximum voronoi cell extent should be between
+ // cellBuffer/2 and cellBuffer in size.
+
+ while (maxSize > cellBuffer)
+ {
+ scaleFactor *= 0.5;
+ maxSize *= 0.5;
+ }
+
+ while (maxSize < (cellBuffer/2))
+ {
+ scaleFactor *= 2;
+ maxSize *= 2;
+ }
+
+ if ((minSize * scaleFactor) > (cellBuffer/2))
+ { // Special correction for objects of near-unity (square-like) aspect ratio,
+ // which have larger area *and* where it is less essential to find the exact centroid:
+ scaleFactor *= 0.5;
+ }
+
+ float StepSize = (1/scaleFactor);
+
+ float xSum = 0;
+ float ySum = 0;
+ float dSum = 0;
+ float PicDensity = 1.0;
+
+
+ if (invertImg)
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 0.001 + (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+ else
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 255.001 - (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+
+ if (dSum > 0)
+ {
+ xSum /= dSum;
+ ySum /= dSum;
+ }
+
+ Vec2D centr;
+
+
+ float xTemp = (xSum);
+ float yTemp = (ySum);
+
+
+ if ((xTemp <= lowBorderX) || (xTemp >= hiBorderX) || (yTemp <= lowBorderY) || (yTemp >= hiBorderY)) {
+ // If new centroid is computed to be outside the visible region, use the geometric centroid instead.
+ // This will help to prevent runaway points due to numerical artifacts.
+ centr = region.getCentroid();
+ xTemp = centr.x;
+ yTemp = centr.y;
+
+ // Enforce sides, if absolutely necessary: (Failure to do so *will* cause a crash, eventually.)
+
+ if (xTemp <= lowBorderX)
+ xTemp = lowBorderX + 1;
+ if (xTemp >= hiBorderX)
+ xTemp = hiBorderX - 1;
+ if (yTemp <= lowBorderY)
+ yTemp = lowBorderY + 1;
+ if (yTemp >= hiBorderY)
+ yTemp = hiBorderY - 1;
+ }
+
+ particles[i].x = xTemp;
+ particles[i].y = yTemp;
+
+ cellsCalculated++;
+ }
+
+
+ // println("cellsCalculated = " + cellsCalculated );
+ // println("cellsTotal = " + cellsTotal );
+
+ if (cellsCalculated >= cellsTotal)
+ {
+ VoronoiCalculated = false;
+ Generation++;
+ println("Generation = " + Generation );
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+ }
+ }
+}
+
+
+void draw()
+{
+
+ int i = 0;
+ int temp;
+ float dotScale = (MaxDotSize - MinDotSize);
+ float cutoffScaled = 1 - cutoff;
+
+ if (ReInitiallizeArray) {
+ maxParticles = (int) cp5.controller("Stipples").value(); // Only change this here!
+
+ MainArraysetup();
+ ReInitiallizeArray = false;
+ }
+
+ if (pausemode && (VoronoiCalculated == false))
+ OptimizePlotPath();
+ else
+ doPhysics();
+
+
+ if (pausemode)
+ {
+
+ DoBackgrounds();
+
+ // Draw paths:
+
+ if ( showPath ) {
+
+ stroke(128, 128, 255); // Stroke color (blue)
+ strokeWeight (1);
+
+ for ( i = 0; i < (particleRouteLength - 1); ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+ Vec2D p2 = particles[particleRoute[i + 1]];
+
+ line(p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+ // Only show "routed" particles-- those above the white cutoff.
+
+ Vec2D p1 = particles[particleRoute[i]];
+ int px = (int) p1.x;
+ int py = (int) p1.y;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ else
+ { // NOT in pause mode. i.e., just displaying stipples.
+ if (cellsCalculated == 0) {
+
+ DoBackgrounds();
+
+ if (Generation == 0)
+ {
+ TempShowCells = true;
+ }
+
+ if (showCells || TempShowCells) { // Draw voronoi cells, over background.
+ strokeWeight(1);
+ noFill();
+
+
+ if (invertImg && (showBG == false)) // TODO -- if invertImg AND NOT background
+ stroke(100);
+ else
+ stroke(200);
+
+ // stroke(200);
+
+ i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ //RegionList[i++] = poly;
+ gfx.polygon2D(clip.clipPolygon(poly));
+ }
+ }
+
+ if (showCells) {
+ // Show "before and after" centroids, when polygons are shown.
+
+ strokeWeight (MinDotSize); // Normal w/ Min & Max dot size
+ for ( i = 0; i < maxParticles; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ //Uncomment the following four lines, if you wish to display the "before" dots at weighted sizes.
+ //float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+ //if (invertImg)
+ //v = 1 - v;
+ //strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+ }
+ else {
+ // Stipple calculation is still underway
+
+ if (TempShowCells)
+ {
+ DoBackgrounds();
+ TempShowCells = false;
+ }
+
+
+ // stroke(0); // Stroke color
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = cellsCalculatedLast; i < cellsCalculated; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ if (v < cutoffScaled) {
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+
+ cellsCalculatedLast = cellsCalculated;
+ }
+ }
+
+ noStroke();
+ fill(100); // Background fill color
+ rect(0, mainheight, mainwidth, height); // Control area fill
+
+ // Underlay for hyperlink:
+ if (overRect(TextColumnStart - 10, mainheight + 35, 205, 20) )
+ {
+ fill(150);
+ rect(TextColumnStart - 10, mainheight + 35, 205, 20);
+ }
+
+ fill(255); // Text color
+
+ text("StippleGen 2 (v. 2.02)", TextColumnStart, mainheight + 15);
+ text("by Evil Mad Scientist Laboratories", TextColumnStart, mainheight + 30);
+ text("www.evilmadscientist.com/go/stipple2", TextColumnStart, mainheight + 50);
+
+ text("Generations completed: " + Generation, TextColumnStart, mainheight + 85);
+ text("Time/Frame: " + frameTime + " s", TextColumnStart, mainheight + 100);
+
+
+ if (ErrorDisp)
+ {
+ fill(255, 0, 0); // Text color
+ text(ErrorDisplay, TextColumnStart, mainheight + 70);
+ if ((millis() - ErrorTime) > 8000)
+ ErrorDisp = false;
+ }
+ else
+ text("Status: " + StatusDisplay, TextColumnStart, mainheight + 70);
+
+
+
+ if (SaveNow > 0) {
+
+ StatusDisplay = "Saving SVG File";
+ SaveNow = 0;
+
+ FileOutput = loadStrings("header.txt");
+
+ String rowTemp;
+
+ float SVGscale = (800.0 / (float) mainheight);
+ int xOffset = (int) (1600 - (SVGscale * mainwidth / 2));
+ int yOffset = (int) (400 - (SVGscale * mainheight / 2));
+
+
+ if (FileModeTSP)
+ { // Plot the PATH between the points only.
+
+ println("Save TSP File (SVG)");
+
+ // Path header::
+ rowTemp = ""); // End path description
+ }
+ else {
+ println("Save Stipple File (SVG)");
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+
+ int px = floor(p1.x);
+ int py = floor(p1.y);
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ float dotrad = (MaxDotSize - v * dotScale)/2;
+
+ float xTemp = SVGscale*p1.x + xOffset;
+ float yTemp = SVGscale*p1.y + yOffset;
+
+ rowTemp = "";
+
+ // Typ:
+
+ FileOutput = append(FileOutput, rowTemp);
+ }
+ }
+
+
+
+ // SVG footer:
+ FileOutput = append(FileOutput, "");
+ saveStrings(savePath, FileOutput);
+ FileModeTSP = false; // reset for next time
+
+ if (FileModeTSP)
+ ErrorDisplay = "TSP Path .SVG file Saved";
+ else
+ ErrorDisplay = "Stipple .SVG file saved ";
+
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+}
+
+
+
+void mousePressed() {
+
+ // rect(TextColumnStart, mainheight, 200, 75);
+
+ if (overRect(TextColumnStart - 15, mainheight + 35, 205, 20) )
+ link("http://www.evilmadscientist.com/go/stipple2");
+}
+
+
+
+
+void keyPressed() {
+ if (key == 'x')
+ { // If this program doesn't run slowly enough for you,
+ // simply press the 'x' key on your keyboard. :)
+ cp5.controller("Stipples").setMax(50000.0);
+ }
+}
+
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Info.plist b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Info.plist
new file mode 100644
index 0000000..46760cf
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Info.plist
@@ -0,0 +1,75 @@
+
+
+
+
+ CFBundleName
+ StippleGen_2
+ CFBundleVersion
+ 1.0
+ CFBundleAllowMixedLocalizations
+ true
+ CFBundleExecutable
+ JavaApplicationStub
+ CFBundleDevelopmentRegion
+ English
+ CFBundlePackageType
+ APPL
+ CFBundleSignature
+ ????
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleIconFile
+ sketch.icns
+ CFBundleIdentifier
+ StippleGen_2
+
+
+ LSUIPresentationMode
+ 0
+
+
+ LSArchitecturePriority
+
+ i386
+ ppc
+
+
+ Java
+
+ VMOptions
+ -Xms64m -Xmx256m
+ MainClass
+ StippleGen_2
+ JVMVersion
+ 1.5*
+ JVMArchs
+
+ i386
+ ppc
+
+ ClassPath
+ $JAVAROOT/StippleGen_2.jar:$JAVAROOT/core.jar:$JAVAROOT/controlP5.jar:$JAVAROOT/toxiclibscore.jar:$JAVAROOT/toxiclibs_p5.jar
+
+
+ Properties
+
+ apple.laf.useScreenMenuBar
+ true
+ apple.awt.showGrowBox
+ false
+ com.apple.smallTabs
+ true
+ apple.awt.Antialiasing
+ false
+ apple.awt.TextAntialiasing
+ true
+ com.apple.hwaccel
+ true
+ apple.awt.use-file-dialog-packages
+ false
+
+
+
+
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/MacOS/JavaApplicationStub b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/MacOS/JavaApplicationStub
new file mode 100644
index 0000000..9ae8d55
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/MacOS/JavaApplicationStub differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/MacOS/JavaApplicationStub64 b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/MacOS/JavaApplicationStub64
new file mode 100644
index 0000000..a8fbda5
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/MacOS/JavaApplicationStub64 differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/PkgInfo b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL????
\ No newline at end of file
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/StippleGen_2.jar b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/StippleGen_2.jar
new file mode 100644
index 0000000..80f52e3
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/StippleGen_2.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/controlP5.jar b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/controlP5.jar
new file mode 100644
index 0000000..a9ac250
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/controlP5.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/core.jar b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/core.jar
new file mode 100644
index 0000000..6cdcb96
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/core.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/toxiclibs_p5.jar b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/toxiclibs_p5.jar
new file mode 100644
index 0000000..bc4d432
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/toxiclibs_p5.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/toxiclibscore.jar b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/toxiclibscore.jar
new file mode 100644
index 0000000..24d5405
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/Java/toxiclibscore.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/sketch.icns b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/sketch.icns
new file mode 100644
index 0000000..6dc82bc
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.macosx/StippleGen_2.app/Contents/Resources/sketch.icns differ
diff --git a/StippleGen_2.02/StippleGen_2/application.macosx/source/StippleGen_2.pde b/StippleGen_2.02/StippleGen_2/application.macosx/source/StippleGen_2.pde
new file mode 100644
index 0000000..40c0771
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.macosx/source/StippleGen_2.pde
@@ -0,0 +1,1373 @@
+/**
+
+ StippleGen_2
+
+ 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
+
+ *******************************************************************************
+
+ Change Log:
+
+ v 2.02
+ * Force files to end in .svg
+ * Fix bug that gave wrong size to stipple files saved white stipples on black background
+
+ v 2.01:
+ * Improved handling of Save process, to prevent accidental "not saving" by users.
+
+ v 2.0:
+ * Add tone reversal option (white on black / black on white)
+ * Reduce vertical extent of GUI, to reduce likelihood of cropping on small screens
+ * Speling corections
+ * Fixed a bug that caused unintended cropping of long, wide images
+ * Reorganized GUI controls
+ * Fail less disgracefully when a bad image type is selected.
+
+ *******************************************************************************
+
+
+
+ Program is based on the Toxic Libs Library ( http://toxiclibs.org/ )
+ & example code:
+ http://forum.processing.org/topic/toxiclib-voronoi-example-sketch
+
+
+ Additional inspiration:
+ Stipple Cam from Jim Bumgardner
+ http://joyofprocessing.com/blog/2011/11/stipple-cam/
+
+ and
+
+ MeshLibDemo.pde - Demo of Lee Byron's Mesh library, by
+ Marius Watz - http://workshop.evolutionzone.com/
+
+
+ Requires ControlP5 library and Toxic Libs library:
+ http://www.sojamo.de/libraries/controlP5/
+ http://hg.postspectacular.com/toxiclibs/downloads
+
+
+ */
+
+
+/*
+ *
+ * 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
+ */
+
+
+// You need the controlP5 library from http://www.sojamo.de/libraries/controlP5/
+import controlP5.*;
+
+//You need the Toxic Libs library: http://hg.postspectacular.com/toxiclibs/downloads
+
+import toxi.geom.*;
+import toxi.geom.mesh2d.*;
+import toxi.util.datatypes.*;
+import toxi.processing.*;
+
+// helper class for rendering
+ToxiclibsSupport gfx;
+
+import javax.swing.UIManager;
+import javax.swing.JFileChooser;
+
+
+
+
+// Feel free to play with these three default settings:
+int maxParticles = 2000; // Max value is normally 10000. Press 'x' key to allow 50000 stipples. (SLOW)
+float MinDotSize = 1.75; //2;
+float DotSizeFactor = 4; //5;
+float cutoff = 0; // White cutoff value
+
+
+int cellBuffer = 100; //Scale each cell to fit in a cellBuffer-sized square window for computing the centroid.
+
+
+// Display window and GUI area sizes:
+int mainwidth;
+int mainheight;
+int borderWidth;
+int ctrlheight;
+int TextColumnStart;
+
+
+
+float lowBorderX;
+float hiBorderX;
+float lowBorderY;
+float hiBorderY;
+
+
+
+float MaxDotSize;
+boolean ReInitiallizeArray;
+boolean pausemode;
+boolean fileLoaded;
+int SaveNow;
+String savePath;
+String[] FileOutput;
+
+
+
+
+String StatusDisplay = "Initializing, please wait. :)";
+float millisLastFrame = 0;
+float frameTime = 0;
+
+String ErrorDisplay = "";
+float ErrorTime;
+Boolean ErrorDisp = false;
+
+
+int Generation;
+int particleRouteLength;
+int RouteStep;
+
+boolean showBG;
+boolean showPath;
+boolean showCells;
+boolean invertImg;
+boolean TempShowCells;
+boolean FileModeTSP;
+
+int vorPointsAdded;
+boolean VoronoiCalculated;
+
+// Toxic libs library setup:
+Voronoi voronoi;
+Polygon2D RegionList[];
+
+PolygonClipper2D clip; // polygon clipper
+
+int cellsTotal, cellsCalculated, cellsCalculatedLast;
+
+
+// ControlP5 GUI library variables setup
+Textlabel ProgName;
+Button OrderOnOff, ImgOnOff, CellOnOff, InvertOnOff, PauseButton;
+ControlP5 cp5;
+
+
+PImage img, imgload, imgblur;
+
+Vec2D[] particles;
+int[] particleRoute;
+
+
+
+void LoadImageAndScale() {
+
+ int tempx = 0;
+ int tempy = 0;
+
+ img = createImage(mainwidth, mainheight, RGB);
+ imgblur = createImage(mainwidth, mainheight, RGB);
+
+ img.loadPixels();
+
+ if (invertImg)
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(0);
+ }
+ else
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(255);
+ }
+
+ img.updatePixels();
+
+ if ( fileLoaded == false) {
+ // Load a demo image, at least until we have a "real" image to work with.
+
+ imgload = loadImage("grace.jpg"); // Load demo image
+ // Image source: http://commons.wikimedia.org/wiki/File:Kelly,_Grace_(Rear_Window).jpg
+ }
+
+ if ((imgload.width > mainwidth) || (imgload.height > mainheight)) {
+
+ if (((float) imgload.width / (float)imgload.height) > ((float) mainwidth / (float) mainheight))
+ {
+ imgload.resize(mainwidth, 0);
+ }
+ else
+ {
+ imgload.resize(0, mainheight);
+ }
+ }
+
+ if (imgload.height < (mainheight - 2) ) {
+ tempy = (int) (( mainheight - imgload.height ) / 2) ;
+ }
+ if (imgload.width < (mainwidth - 2)) {
+ tempx = (int) (( mainwidth - imgload.width ) / 2) ;
+ }
+
+ img.copy(imgload, 0, 0, imgload.width, imgload.height, tempx, tempy, imgload.width, imgload.height);
+ // For background image!
+
+
+ /*
+ // Optional gamma correction for background image.
+ img.loadPixels();
+
+ float tempFloat;
+ float GammaValue = 1.0; // Normally in the range 0.25 - 4.0
+
+ for (int i = 0; i < img.pixels.length; i++) {
+ tempFloat = brightness(img.pixels[i])/255;
+ img.pixels[i] = color(floor(255 * pow(tempFloat,GammaValue)));
+ }
+ img.updatePixels();
+ */
+
+
+ imgblur.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
+ // This is a duplicate of the background image, that we will apply a blur to,
+ // to reduce "high frequency" noise artifacts.
+
+ imgblur.filter(BLUR, 1); // Low-level blur filter to elminate pixel-to-pixel noise artifacts.
+ imgblur.loadPixels();
+}
+
+
+void MainArraysetup() {
+ // Main particle array initialization (to be called whenever necessary):
+
+ LoadImageAndScale();
+
+ // image(img, 0, 0); // SHOW BG IMG
+
+ particles = new Vec2D[maxParticles];
+
+
+ // Fill array by "rejection sampling"
+ int i = 0;
+ while (i < maxParticles)
+ {
+
+ float fx = lowBorderX + random(hiBorderX - lowBorderX);
+ float fy = lowBorderY + random(hiBorderY - lowBorderY);
+
+ float p = brightness(imgblur.pixels[ floor(fy)*imgblur.width + floor(fx) ])/255;
+ // OK to use simple floor_ rounding here, because this is a one-time operation,
+ // creating the initial distribution that will be iterated.
+
+ if (invertImg)
+ {
+ p = 1 - p;
+ }
+
+ if (random(1) >= p ) {
+ Vec2D p1 = new Vec2D(fx, fy);
+ particles[i] = p1;
+ i++;
+ }
+ }
+
+ particleRouteLength = 0;
+ Generation = 0;
+ millisLastFrame = millis();
+ RouteStep = 0;
+ VoronoiCalculated = false;
+ cellsCalculated = 0;
+ vorPointsAdded = 0;
+ voronoi = new Voronoi(); // Erase mesh
+ TempShowCells = true;
+ FileModeTSP = false;
+}
+
+void setup()
+{
+
+ borderWidth = 6;
+
+ mainwidth = 800;
+ mainheight = 600;
+ ctrlheight = 110;
+
+ size(mainwidth, mainheight + ctrlheight, JAVA2D);
+
+ gfx = new ToxiclibsSupport(this);
+
+
+ lowBorderX = borderWidth; //mainwidth*0.01;
+ hiBorderX = mainwidth - borderWidth; //mainwidth*0.98;
+ lowBorderY = borderWidth; // mainheight*0.01;
+ hiBorderY = mainheight - borderWidth; //mainheight*0.98;
+
+ int innerWidth = mainwidth - 2 * borderWidth;
+ int innerHeight = mainheight - 2 * borderWidth;
+
+ clip=new SutherlandHodgemanClipper(new Rect(lowBorderX, lowBorderY, innerWidth, innerHeight));
+
+ MainArraysetup(); // Main particle array setup
+
+ frameRate(24);
+
+ smooth();
+ noStroke();
+ fill(153); // Background fill color, for control section
+
+ textFont(createFont("SansSerif", 10));
+
+
+ cp5 = new ControlP5(this);
+
+ int leftcolumwidth = 225;
+
+ int GUItop = mainheight + 15;
+ int GUI2ndRow = 4; // Spacing for firt row after group heading
+ int GuiRowSpacing = 14; // Spacing for subsequent rows
+ int GUIFudge = mainheight + 19; // I wish that we didn't need ONE MORE of these stupid spacings.
+
+
+ ControlGroup l3 = cp5.addGroup("Primary controls (Changing will restart)", 10, GUItop, 225);
+
+ cp5.addSlider("Stipples", 10, 10000, maxParticles, 10, GUI2ndRow, 150, 10).setGroup(l3);
+
+ InvertOnOff = cp5.addButton("INVERT_IMG", 10, 10, GUI2ndRow + GuiRowSpacing, 190, 10).setGroup(l3);
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+
+
+ Button LoadButton = cp5.addButton("LOAD_FILE", 10, 10, GUIFudge + 3*GuiRowSpacing, 175, 10);
+ LoadButton.setCaptionLabel("LOAD IMAGE FILE (.PNG, .JPG, or .GIF)");
+
+ cp5.addButton("QUIT", 10, 205, GUIFudge + 3*GuiRowSpacing, 30, 10);
+
+ cp5.addButton("SAVE_STIPPLES", 10, 25, GUIFudge + 4*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_STIPPLES").setCaptionLabel("Save Stipple File (.SVG format)");
+
+ cp5.addButton("SAVE_PATH", 10, 25, GUIFudge + 5*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_PATH").setCaptionLabel("Save \"TSP\" Path (.SVG format)");
+
+
+ ControlGroup l5 = cp5.addGroup("Display Options - Updated on next generation", leftcolumwidth+50, GUItop, 225);
+
+ cp5.addSlider("Min_Dot_Size", .5, 8, 2, 10, 4, 140, 10).setGroup(l5);
+ cp5.controller("Min_Dot_Size").setValue(MinDotSize);
+ cp5.controller("Min_Dot_Size").setCaptionLabel("Min. Dot Size");
+
+ cp5.addSlider("Dot_Size_Range", 0, 20, 5, 10, 18, 140, 10).setGroup(l5);
+ cp5.controller("Dot_Size_Range").setValue(DotSizeFactor);
+ cp5.controller("Dot_Size_Range").setCaptionLabel("Dot Size Range");
+
+ cp5.addSlider("White_Cutoff", 0, 1, 0, 10, 32, 140, 10).setGroup(l5);
+ cp5.controller("White_Cutoff").setValue(cutoff);
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+
+
+ ImgOnOff = cp5.addButton("IMG_ON_OFF", 10, 10, 46, 90, 10);
+ ImgOnOff.setGroup(l5);
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+
+ CellOnOff = cp5.addButton("CELLS_ON_OFF", 10, 110, 46, 90, 10);
+ CellOnOff.setGroup(l5);
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+
+ PauseButton = cp5.addButton("Pause", 10, 10, 60, 190, 10);
+ PauseButton.setGroup(l5);
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+
+ OrderOnOff = cp5.addButton("ORDER_ON_OFF", 10, 10, 74, 190, 10);
+ OrderOnOff.setGroup(l5);
+ OrderOnOff.setCaptionLabel("Plotting path >> shown while paused");
+
+
+
+
+
+ TextColumnStart = 2 * leftcolumwidth + 100;
+
+ MaxDotSize = MinDotSize * (1 + DotSizeFactor);
+
+ ReInitiallizeArray = false;
+ pausemode = false;
+ showBG = false;
+ invertImg = false;
+ showPath = true;
+ showCells = false;
+ fileLoaded = false;
+ SaveNow = 0;
+}
+
+
+void LOAD_FILE(float theValue) {
+ println(":::LOAD JPG, GIF or PNG FILE:::");
+
+ String loadPath = selectInput(); // Opens file chooser
+ if (loadPath == null) {
+ // If a file was not selected
+ println("No file was selected...");
+ }
+ else {
+ // If a file was selected, print path to file
+ println("Loaded file: " + loadPath);
+
+
+ String[] p = splitTokens(loadPath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("GIF"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("gif"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("JPG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("jpg"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("TGA"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("tga"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("PNG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("png"))
+ fileOK = true;
+
+ println("File OK: " + fileOK);
+
+ if (fileOK) {
+ imgload = loadImage(loadPath);
+ fileLoaded = true;
+ // MainArraysetup();
+ ReInitiallizeArray = true;
+ }
+ else {
+ // Can't load file
+ ErrorDisplay = "ERROR: BAD FILE TYPE";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+} //End Load File
+
+
+
+void SAVE_PATH(float theValue) {
+ FileModeTSP = true;
+ SAVE_SVG(0);
+}
+
+
+
+void SAVE_STIPPLES(float theValue) {
+ FileModeTSP = false;
+ SAVE_SVG(0);
+}
+
+
+
+
+
+
+void SAVE_SVG(float theValue) {
+
+
+
+ if (pausemode != true) {
+ Pause(0.0);
+ ErrorDisplay = "Error: PAUSE before saving.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+ savePath = selectOutput("Output .svg file name:"); // Opens file chooser
+ if (savePath == null) {
+ // If a file was not selected
+ println("No output file was selected...");
+ ErrorDisplay = "ERROR: NO FILE NAME CHOSEN.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+
+ String[] p = splitTokens(savePath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("SVG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("svg"))
+ fileOK = true;
+
+ if (fileOK == false)
+ savePath = savePath + ".svg";
+
+
+ // If a file was selected, print path to folder
+ println("Save file: " + savePath);
+ SaveNow = 1;
+ showPath = true;
+
+ ErrorDisplay = "SAVING FILE...";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+}
+
+
+
+
+void QUIT(float theValue) {
+ exit();
+}
+
+
+void ORDER_ON_OFF(float theValue) {
+ if (showPath) {
+ showPath = false;
+ OrderOnOff.setCaptionLabel("Plotting path >> Hide");
+ }
+ else {
+ showPath = true;
+ OrderOnOff.setCaptionLabel("Plotting path >> Shown while paused");
+ }
+}
+
+void CELLS_ON_OFF(float theValue) {
+ if (showCells) {
+ showCells = false;
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+ }
+ else {
+ showCells = true;
+ CellOnOff.setCaptionLabel("Cells >> Show");
+ }
+}
+
+
+
+void IMG_ON_OFF(float theValue) {
+ if (showBG) {
+ showBG = false;
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+ }
+ else {
+ showBG = true;
+ ImgOnOff.setCaptionLabel("Image BG >> Show");
+ }
+}
+
+
+void INVERT_IMG(float theValue) {
+ if (invertImg) {
+ invertImg = false;
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+ }
+ else {
+ invertImg = true;
+ InvertOnOff.setCaptionLabel("White stipples, Black Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("Black Cutoff");
+ }
+
+ ReInitiallizeArray = true;
+ pausemode = false;
+}
+
+
+
+
+void Pause(float theValue) {
+ // Main particle array setup (to be repeated if necessary):
+
+ if (pausemode)
+ {
+ pausemode = false;
+ println("Resuming.");
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+ }
+ else
+ {
+ pausemode = true;
+ println("Paused. Press PAUSE again to resume.");
+ PauseButton.setCaptionLabel("Paused (calculating TSP path)");
+ }
+ RouteStep = 0;
+}
+
+
+boolean overRect(int x, int y, int width, int height)
+{
+ if (mouseX >= x && mouseX <= x+width &&
+ mouseY >= y && mouseY <= y+height) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+void Stipples(int inValue) {
+
+ if (maxParticles != (int) inValue) {
+ println("Update: Stipple Count -> " + inValue);
+ ReInitiallizeArray = true;
+ pausemode = false;
+ }
+}
+
+
+
+
+
+void Min_Dot_Size(float inValue) {
+ if (MinDotSize != inValue) {
+ println("Update: Min_Dot_Size -> "+inValue);
+ MinDotSize = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void Dot_Size_Range(float inValue) {
+ if (DotSizeFactor != inValue) {
+ println("Update: Dot Size Range -> "+inValue);
+ DotSizeFactor = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void White_Cutoff(float inValue) {
+ if (cutoff != inValue) {
+ println("Update: White_Cutoff -> "+inValue);
+ cutoff = inValue;
+ RouteStep = 0; // Reset TSP path
+ }
+}
+
+
+void DoBackgrounds() {
+ if (showBG)
+ image(img, 0, 0); // Show original (cropped and scaled, but not blurred!) image in background
+ else {
+
+ if (invertImg)
+ fill(0);
+ else
+ fill(255);
+
+ rect(0, 0, mainwidth, mainheight);
+ }
+}
+
+void OptimizePlotPath()
+{
+ int temp;
+ // Calculate and show "optimized" plotting path, beneath points.
+
+ StatusDisplay = "Optimizing plotting path";
+ /*
+ if (RouteStep % 100 == 0) {
+ println("RouteStep:" + RouteStep);
+ println("fps = " + frameRate );
+ }
+ */
+
+ Vec2D p1;
+
+
+ if (RouteStep == 0)
+ {
+
+ float cutoffScaled = 1 - cutoff;
+ // Begin process of optimizing plotting route, by flagging particles that will be shown.
+
+ particleRouteLength = 0;
+
+ boolean particleRouteTemp[] = new boolean[maxParticles];
+
+ for (int i = 0; i < maxParticles; ++i) {
+
+ particleRouteTemp[i] = false;
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+
+ if (v < cutoffScaled) {
+ particleRouteTemp[i] = true;
+ particleRouteLength++;
+ }
+ }
+
+ particleRoute = new int[particleRouteLength];
+ int tempCounter = 0;
+ for (int i = 0; i < maxParticles; ++i) {
+
+ if (particleRouteTemp[i])
+ {
+ particleRoute[tempCounter] = i;
+ tempCounter++;
+ }
+ }
+ // These are the ONLY points to be drawn in the tour.
+ }
+
+ if (RouteStep < (particleRouteLength - 2))
+ {
+
+ // Nearest neighbor ("Simple, Greedy") algorithm path optimization:
+
+ int StopPoint = RouteStep + 1000; // 1000 steps per frame displayed; you can edit this number!
+
+ if (StopPoint > (particleRouteLength - 1))
+ StopPoint = particleRouteLength - 1;
+
+ for (int i = RouteStep; i < StopPoint; ++i) {
+
+ p1 = particles[particleRoute[RouteStep]];
+ int ClosestParticle = 0;
+ float distMin = Float.MAX_VALUE;
+
+ for (int j = RouteStep + 1; j < (particleRouteLength - 1); ++j) {
+ Vec2D p2 = particles[particleRoute[j]];
+
+ float dx = p1.x - p2.x;
+ float dy = p1.y - p2.y;
+ float distance = (float) (dx*dx+dy*dy); // Only looking for closest; do not need sqrt factor!
+
+ if (distance < distMin) {
+ ClosestParticle = j;
+ distMin = distance;
+ }
+ }
+
+ temp = particleRoute[RouteStep + 1];
+ // p1 = particles[particleRoute[RouteStep + 1]];
+ particleRoute[RouteStep + 1] = particleRoute[ClosestParticle];
+ particleRoute[ClosestParticle] = temp;
+
+ if (RouteStep < (particleRouteLength - 1))
+ RouteStep++;
+ else
+ {
+ println("Now optimizing plot path" );
+ }
+ }
+ }
+ else
+ { // Initial routing is complete
+ // 2-opt heuristic optimization:
+ // Identify a pair of edges that would become shorter by reversing part of the tour.
+
+ for (int i = 0; i < 90000; ++i) { // 1000 tests per frame; you can edit this number.
+
+ int indexA = floor(random(particleRouteLength - 1));
+ int indexB = floor(random(particleRouteLength - 1));
+
+ if (Math.abs(indexA - indexB) < 2)
+ continue;
+
+ if (indexB < indexA)
+ { // swap A, B.
+ temp = indexB;
+ indexB = indexA;
+ indexA = temp;
+ }
+
+ Vec2D a0 = particles[particleRoute[indexA]];
+ Vec2D a1 = particles[particleRoute[indexA + 1]];
+ Vec2D b0 = particles[particleRoute[indexB]];
+ Vec2D b1 = particles[particleRoute[indexB + 1]];
+
+ // Original distance:
+ float dx = a0.x - a1.x;
+ float dy = a0.y - a1.y;
+ float distance = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = b0.x - b1.x;
+ dy = b0.y - b1.y;
+ distance += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ // Possible shorter distance?
+ dx = a0.x - b0.x;
+ dy = a0.y - b0.y;
+ float distance2 = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = a1.x - b1.x;
+ dy = a1.y - b1.y;
+ distance2 += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ if (distance2 < distance)
+ {
+ // Reverse tour between a1 and b0.
+
+ int indexhigh = indexB;
+ int indexlow = indexA + 1;
+
+ // println("Shorten!" + frameRate );
+
+ while (indexhigh > indexlow)
+ {
+
+ temp = particleRoute[indexlow];
+ particleRoute[indexlow] = particleRoute[indexhigh];
+ particleRoute[indexhigh] = temp;
+
+ indexhigh--;
+ indexlow++;
+ }
+ }
+ }
+ }
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+}
+
+
+
+
+
+
+
+void doPhysics()
+{ // Iterative relaxation via weighted Lloyd's algorithm.
+
+ int temp;
+ int CountTemp;
+
+ if (VoronoiCalculated == false)
+ { // Part I: Calculate voronoi cell diagram of the points.
+
+ StatusDisplay = "Calculating Voronoi diagram ";
+
+ // float millisBaseline = millis(); // Baseline for timing studies
+ // println("Baseline. Time = " + (millis() - millisBaseline) );
+
+
+ if (vorPointsAdded == 0)
+ voronoi = new Voronoi(); // Erase mesh
+
+ temp = vorPointsAdded + 200; // This line: VoronoiPointsPerPass (Feel free to edit this number.)
+ if (temp > maxParticles)
+ temp = maxParticles;
+
+ for (int i = vorPointsAdded; i < temp; ++i) {
+
+
+ // Optional, for diagnostics:::
+ // println("particles[i].x, particles[i].y " + particles[i].x + ", " + particles[i].y );
+
+
+ //
+
+
+ voronoi.addPoint(new Vec2D(particles[i].x, particles[i].y ));
+ vorPointsAdded++;
+ }
+
+ if (vorPointsAdded >= maxParticles)
+ {
+
+ // println("Points added. Time = " + (millis() - millisBaseline) );
+
+ cellsTotal = (voronoi.getRegions().size());
+ vorPointsAdded = 0;
+ cellsCalculated = 0;
+ cellsCalculatedLast = 0;
+
+ RegionList = new Polygon2D[cellsTotal];
+
+ int i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ RegionList[i++] = poly; // Build array of polygons
+ }
+ VoronoiCalculated = true;
+ }
+ }
+ else
+ { // Part II: Calculate weighted centroids of cells.
+ // float millisBaseline = millis();
+ // println("fps = " + frameRate );
+
+ StatusDisplay = "Calculating weighted centroids";
+
+
+ temp = cellsCalculated + 100; // This line: CentroidsPerPass (Feel free to edit this number.)
+ // Higher values give slightly faster computation, but a less responsive GUI.
+
+
+ if (temp > cellsTotal)
+ {
+ temp = cellsTotal;
+ }
+
+ for (int i=cellsCalculated; i< temp; i++) {
+
+ float xMax = 0;
+ float xMin = mainwidth;
+ float yMax = 0;
+ float yMin = mainheight;
+ float xt, yt;
+
+ Polygon2D region = clip.clipPolygon(RegionList[i]);
+
+
+ for (Vec2D v : region.vertices) {
+
+ xt = v.x;
+ yt = v.y;
+
+ if (xt < xMin)
+ xMin = xt;
+ if (xt > xMax)
+ xMax = xt;
+ if (yt < yMin)
+ yMin = yt;
+ if (yt > yMax)
+ yMax = yt;
+ }
+
+ float xDiff = xMax - xMin;
+ float yDiff = yMax - yMin;
+ float maxSize = max(xDiff, yDiff);
+ float minSize = min(xDiff, yDiff);
+
+ float scaleFactor = 1.0;
+
+ // Maximum voronoi cell extent should be between
+ // cellBuffer/2 and cellBuffer in size.
+
+ while (maxSize > cellBuffer)
+ {
+ scaleFactor *= 0.5;
+ maxSize *= 0.5;
+ }
+
+ while (maxSize < (cellBuffer/2))
+ {
+ scaleFactor *= 2;
+ maxSize *= 2;
+ }
+
+ if ((minSize * scaleFactor) > (cellBuffer/2))
+ { // Special correction for objects of near-unity (square-like) aspect ratio,
+ // which have larger area *and* where it is less essential to find the exact centroid:
+ scaleFactor *= 0.5;
+ }
+
+ float StepSize = (1/scaleFactor);
+
+ float xSum = 0;
+ float ySum = 0;
+ float dSum = 0;
+ float PicDensity = 1.0;
+
+
+ if (invertImg)
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 0.001 + (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+ else
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 255.001 - (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+
+ if (dSum > 0)
+ {
+ xSum /= dSum;
+ ySum /= dSum;
+ }
+
+ Vec2D centr;
+
+
+ float xTemp = (xSum);
+ float yTemp = (ySum);
+
+
+ if ((xTemp <= lowBorderX) || (xTemp >= hiBorderX) || (yTemp <= lowBorderY) || (yTemp >= hiBorderY)) {
+ // If new centroid is computed to be outside the visible region, use the geometric centroid instead.
+ // This will help to prevent runaway points due to numerical artifacts.
+ centr = region.getCentroid();
+ xTemp = centr.x;
+ yTemp = centr.y;
+
+ // Enforce sides, if absolutely necessary: (Failure to do so *will* cause a crash, eventually.)
+
+ if (xTemp <= lowBorderX)
+ xTemp = lowBorderX + 1;
+ if (xTemp >= hiBorderX)
+ xTemp = hiBorderX - 1;
+ if (yTemp <= lowBorderY)
+ yTemp = lowBorderY + 1;
+ if (yTemp >= hiBorderY)
+ yTemp = hiBorderY - 1;
+ }
+
+ particles[i].x = xTemp;
+ particles[i].y = yTemp;
+
+ cellsCalculated++;
+ }
+
+
+ // println("cellsCalculated = " + cellsCalculated );
+ // println("cellsTotal = " + cellsTotal );
+
+ if (cellsCalculated >= cellsTotal)
+ {
+ VoronoiCalculated = false;
+ Generation++;
+ println("Generation = " + Generation );
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+ }
+ }
+}
+
+
+void draw()
+{
+
+ int i = 0;
+ int temp;
+ float dotScale = (MaxDotSize - MinDotSize);
+ float cutoffScaled = 1 - cutoff;
+
+ if (ReInitiallizeArray) {
+ maxParticles = (int) cp5.controller("Stipples").value(); // Only change this here!
+
+ MainArraysetup();
+ ReInitiallizeArray = false;
+ }
+
+ if (pausemode && (VoronoiCalculated == false))
+ OptimizePlotPath();
+ else
+ doPhysics();
+
+
+ if (pausemode)
+ {
+
+ DoBackgrounds();
+
+ // Draw paths:
+
+ if ( showPath ) {
+
+ stroke(128, 128, 255); // Stroke color (blue)
+ strokeWeight (1);
+
+ for ( i = 0; i < (particleRouteLength - 1); ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+ Vec2D p2 = particles[particleRoute[i + 1]];
+
+ line(p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+ // Only show "routed" particles-- those above the white cutoff.
+
+ Vec2D p1 = particles[particleRoute[i]];
+ int px = (int) p1.x;
+ int py = (int) p1.y;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ else
+ { // NOT in pause mode. i.e., just displaying stipples.
+ if (cellsCalculated == 0) {
+
+ DoBackgrounds();
+
+ if (Generation == 0)
+ {
+ TempShowCells = true;
+ }
+
+ if (showCells || TempShowCells) { // Draw voronoi cells, over background.
+ strokeWeight(1);
+ noFill();
+
+
+ if (invertImg && (showBG == false)) // TODO -- if invertImg AND NOT background
+ stroke(100);
+ else
+ stroke(200);
+
+ // stroke(200);
+
+ i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ //RegionList[i++] = poly;
+ gfx.polygon2D(clip.clipPolygon(poly));
+ }
+ }
+
+ if (showCells) {
+ // Show "before and after" centroids, when polygons are shown.
+
+ strokeWeight (MinDotSize); // Normal w/ Min & Max dot size
+ for ( i = 0; i < maxParticles; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ //Uncomment the following four lines, if you wish to display the "before" dots at weighted sizes.
+ //float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+ //if (invertImg)
+ //v = 1 - v;
+ //strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+ }
+ else {
+ // Stipple calculation is still underway
+
+ if (TempShowCells)
+ {
+ DoBackgrounds();
+ TempShowCells = false;
+ }
+
+
+ // stroke(0); // Stroke color
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = cellsCalculatedLast; i < cellsCalculated; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ if (v < cutoffScaled) {
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+
+ cellsCalculatedLast = cellsCalculated;
+ }
+ }
+
+ noStroke();
+ fill(100); // Background fill color
+ rect(0, mainheight, mainwidth, height); // Control area fill
+
+ // Underlay for hyperlink:
+ if (overRect(TextColumnStart - 10, mainheight + 35, 205, 20) )
+ {
+ fill(150);
+ rect(TextColumnStart - 10, mainheight + 35, 205, 20);
+ }
+
+ fill(255); // Text color
+
+ text("StippleGen 2 (v. 2.02)", TextColumnStart, mainheight + 15);
+ text("by Evil Mad Scientist Laboratories", TextColumnStart, mainheight + 30);
+ text("www.evilmadscientist.com/go/stipple2", TextColumnStart, mainheight + 50);
+
+ text("Generations completed: " + Generation, TextColumnStart, mainheight + 85);
+ text("Time/Frame: " + frameTime + " s", TextColumnStart, mainheight + 100);
+
+
+ if (ErrorDisp)
+ {
+ fill(255, 0, 0); // Text color
+ text(ErrorDisplay, TextColumnStart, mainheight + 70);
+ if ((millis() - ErrorTime) > 8000)
+ ErrorDisp = false;
+ }
+ else
+ text("Status: " + StatusDisplay, TextColumnStart, mainheight + 70);
+
+
+
+ if (SaveNow > 0) {
+
+ StatusDisplay = "Saving SVG File";
+ SaveNow = 0;
+
+ FileOutput = loadStrings("header.txt");
+
+ String rowTemp;
+
+ float SVGscale = (800.0 / (float) mainheight);
+ int xOffset = (int) (1600 - (SVGscale * mainwidth / 2));
+ int yOffset = (int) (400 - (SVGscale * mainheight / 2));
+
+
+ if (FileModeTSP)
+ { // Plot the PATH between the points only.
+
+ println("Save TSP File (SVG)");
+
+ // Path header::
+ rowTemp = ""); // End path description
+ }
+ else {
+ println("Save Stipple File (SVG)");
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+
+ int px = floor(p1.x);
+ int py = floor(p1.y);
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ float dotrad = (MaxDotSize - v * dotScale)/2;
+
+ float xTemp = SVGscale*p1.x + xOffset;
+ float yTemp = SVGscale*p1.y + yOffset;
+
+ rowTemp = "";
+
+ // Typ:
+
+ FileOutput = append(FileOutput, rowTemp);
+ }
+ }
+
+
+
+ // SVG footer:
+ FileOutput = append(FileOutput, "");
+ saveStrings(savePath, FileOutput);
+ FileModeTSP = false; // reset for next time
+
+ if (FileModeTSP)
+ ErrorDisplay = "TSP Path .SVG file Saved";
+ else
+ ErrorDisplay = "Stipple .SVG file saved ";
+
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+}
+
+
+
+void mousePressed() {
+
+ // rect(TextColumnStart, mainheight, 200, 75);
+
+ if (overRect(TextColumnStart - 15, mainheight + 35, 205, 20) )
+ link("http://www.evilmadscientist.com/go/stipple2");
+}
+
+
+
+
+void keyPressed() {
+ if (key == 'x')
+ { // If this program doesn't run slowly enough for you,
+ // simply press the 'x' key on your keyboard. :)
+ cp5.controller("Stipples").setMax(50000.0);
+ }
+}
+
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/StippleGen_2.exe b/StippleGen_2.02/StippleGen_2/application.windows/StippleGen_2.exe
new file mode 100644
index 0000000..ea70a3a
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.windows/StippleGen_2.exe differ
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/lib/StippleGen_2.jar b/StippleGen_2.02/StippleGen_2/application.windows/lib/StippleGen_2.jar
new file mode 100644
index 0000000..80f52e3
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.windows/lib/StippleGen_2.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/lib/args.txt b/StippleGen_2.02/StippleGen_2/application.windows/lib/args.txt
new file mode 100644
index 0000000..dfda833
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.windows/lib/args.txt
@@ -0,0 +1,3 @@
+ -Xms64m -Xmx256m
+StippleGen_2
+StippleGen_2.jar,core.jar,controlP5.jar,toxiclibscore.jar,toxiclibs_p5.jar
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/lib/controlP5.jar b/StippleGen_2.02/StippleGen_2/application.windows/lib/controlP5.jar
new file mode 100644
index 0000000..a9ac250
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.windows/lib/controlP5.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/lib/core.jar b/StippleGen_2.02/StippleGen_2/application.windows/lib/core.jar
new file mode 100644
index 0000000..6cdcb96
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.windows/lib/core.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/lib/toxiclibs_p5.jar b/StippleGen_2.02/StippleGen_2/application.windows/lib/toxiclibs_p5.jar
new file mode 100644
index 0000000..bc4d432
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.windows/lib/toxiclibs_p5.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/lib/toxiclibscore.jar b/StippleGen_2.02/StippleGen_2/application.windows/lib/toxiclibscore.jar
new file mode 100644
index 0000000..24d5405
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/application.windows/lib/toxiclibscore.jar differ
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/source/StippleGen_2.java b/StippleGen_2.02/StippleGen_2/application.windows/source/StippleGen_2.java
new file mode 100644
index 0000000..de10947
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.windows/source/StippleGen_2.java
@@ -0,0 +1,1404 @@
+import processing.core.*;
+import processing.xml.*;
+
+import controlP5.*;
+import toxi.geom.*;
+import toxi.geom.mesh2d.*;
+import toxi.util.datatypes.*;
+import toxi.processing.*;
+import javax.swing.UIManager;
+import javax.swing.JFileChooser;
+
+import java.applet.*;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.event.MouseEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.FocusEvent;
+import java.awt.Image;
+import java.io.*;
+import java.net.*;
+import java.text.*;
+import java.util.*;
+import java.util.zip.*;
+import java.util.regex.*;
+
+public class StippleGen_2 extends PApplet {
+
+/**
+
+ StippleGen_2
+
+ 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
+
+ *******************************************************************************
+
+ Change Log:
+
+ v 2.02
+ * Force files to end in .svg
+ * Fix bug that gave wrong size to stipple files saved white stipples on black background
+
+ v 2.01:
+ * Improved handling of Save process, to prevent accidental "not saving" by users.
+
+ v 2.0:
+ * Add tone reversal option (white on black / black on white)
+ * Reduce vertical extent of GUI, to reduce likelihood of cropping on small screens
+ * Speling corections
+ * Fixed a bug that caused unintended cropping of long, wide images
+ * Reorganized GUI controls
+ * Fail less disgracefully when a bad image type is selected.
+
+ *******************************************************************************
+
+
+
+ Program is based on the Toxic Libs Library ( http://toxiclibs.org/ )
+ & example code:
+ http://forum.processing.org/topic/toxiclib-voronoi-example-sketch
+
+
+ Additional inspiration:
+ Stipple Cam from Jim Bumgardner
+ http://joyofprocessing.com/blog/2011/11/stipple-cam/
+
+ and
+
+ MeshLibDemo.pde - Demo of Lee Byron's Mesh library, by
+ Marius Watz - http://workshop.evolutionzone.com/
+
+
+ Requires ControlP5 library and Toxic Libs library:
+ http://www.sojamo.de/libraries/controlP5/
+ http://hg.postspectacular.com/toxiclibs/downloads
+
+
+ */
+
+
+/*
+ *
+ * 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
+ */
+
+
+// You need the controlP5 library from http://www.sojamo.de/libraries/controlP5/
+
+
+//You need the Toxic Libs library: http://hg.postspectacular.com/toxiclibs/downloads
+
+
+
+
+
+
+// helper class for rendering
+ToxiclibsSupport gfx;
+
+
+
+
+
+
+
+// Feel free to play with these three default settings:
+int maxParticles = 2000; // Max value is normally 10000. Press 'x' key to allow 50000 stipples. (SLOW)
+float MinDotSize = 1.75f; //2;
+float DotSizeFactor = 4; //5;
+float cutoff = 0; // White cutoff value
+
+
+int cellBuffer = 100; //Scale each cell to fit in a cellBuffer-sized square window for computing the centroid.
+
+
+// Display window and GUI area sizes:
+int mainwidth;
+int mainheight;
+int borderWidth;
+int ctrlheight;
+int TextColumnStart;
+
+
+
+float lowBorderX;
+float hiBorderX;
+float lowBorderY;
+float hiBorderY;
+
+
+
+float MaxDotSize;
+boolean ReInitiallizeArray;
+boolean pausemode;
+boolean fileLoaded;
+int SaveNow;
+String savePath;
+String[] FileOutput;
+
+
+
+
+String StatusDisplay = "Initializing, please wait. :)";
+float millisLastFrame = 0;
+float frameTime = 0;
+
+String ErrorDisplay = "";
+float ErrorTime;
+Boolean ErrorDisp = false;
+
+
+int Generation;
+int particleRouteLength;
+int RouteStep;
+
+boolean showBG;
+boolean showPath;
+boolean showCells;
+boolean invertImg;
+boolean TempShowCells;
+boolean FileModeTSP;
+
+int vorPointsAdded;
+boolean VoronoiCalculated;
+
+// Toxic libs library setup:
+Voronoi voronoi;
+Polygon2D RegionList[];
+
+PolygonClipper2D clip; // polygon clipper
+
+int cellsTotal, cellsCalculated, cellsCalculatedLast;
+
+
+// ControlP5 GUI library variables setup
+Textlabel ProgName;
+Button OrderOnOff, ImgOnOff, CellOnOff, InvertOnOff, PauseButton;
+ControlP5 cp5;
+
+
+PImage img, imgload, imgblur;
+
+Vec2D[] particles;
+int[] particleRoute;
+
+
+
+public void LoadImageAndScale() {
+
+ int tempx = 0;
+ int tempy = 0;
+
+ img = createImage(mainwidth, mainheight, RGB);
+ imgblur = createImage(mainwidth, mainheight, RGB);
+
+ img.loadPixels();
+
+ if (invertImg)
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(0);
+ }
+ else
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(255);
+ }
+
+ img.updatePixels();
+
+ if ( fileLoaded == false) {
+ // Load a demo image, at least until we have a "real" image to work with.
+
+ imgload = loadImage("grace.jpg"); // Load demo image
+ // Image source: http://commons.wikimedia.org/wiki/File:Kelly,_Grace_(Rear_Window).jpg
+ }
+
+ if ((imgload.width > mainwidth) || (imgload.height > mainheight)) {
+
+ if (((float) imgload.width / (float)imgload.height) > ((float) mainwidth / (float) mainheight))
+ {
+ imgload.resize(mainwidth, 0);
+ }
+ else
+ {
+ imgload.resize(0, mainheight);
+ }
+ }
+
+ if (imgload.height < (mainheight - 2) ) {
+ tempy = (int) (( mainheight - imgload.height ) / 2) ;
+ }
+ if (imgload.width < (mainwidth - 2)) {
+ tempx = (int) (( mainwidth - imgload.width ) / 2) ;
+ }
+
+ img.copy(imgload, 0, 0, imgload.width, imgload.height, tempx, tempy, imgload.width, imgload.height);
+ // For background image!
+
+
+ /*
+ // Optional gamma correction for background image.
+ img.loadPixels();
+
+ float tempFloat;
+ float GammaValue = 1.0; // Normally in the range 0.25 - 4.0
+
+ for (int i = 0; i < img.pixels.length; i++) {
+ tempFloat = brightness(img.pixels[i])/255;
+ img.pixels[i] = color(floor(255 * pow(tempFloat,GammaValue)));
+ }
+ img.updatePixels();
+ */
+
+
+ imgblur.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
+ // This is a duplicate of the background image, that we will apply a blur to,
+ // to reduce "high frequency" noise artifacts.
+
+ imgblur.filter(BLUR, 1); // Low-level blur filter to elminate pixel-to-pixel noise artifacts.
+ imgblur.loadPixels();
+}
+
+
+public void MainArraysetup() {
+ // Main particle array initialization (to be called whenever necessary):
+
+ LoadImageAndScale();
+
+ // image(img, 0, 0); // SHOW BG IMG
+
+ particles = new Vec2D[maxParticles];
+
+
+ // Fill array by "rejection sampling"
+ int i = 0;
+ while (i < maxParticles)
+ {
+
+ float fx = lowBorderX + random(hiBorderX - lowBorderX);
+ float fy = lowBorderY + random(hiBorderY - lowBorderY);
+
+ float p = brightness(imgblur.pixels[ floor(fy)*imgblur.width + floor(fx) ])/255;
+ // OK to use simple floor_ rounding here, because this is a one-time operation,
+ // creating the initial distribution that will be iterated.
+
+ if (invertImg)
+ {
+ p = 1 - p;
+ }
+
+ if (random(1) >= p ) {
+ Vec2D p1 = new Vec2D(fx, fy);
+ particles[i] = p1;
+ i++;
+ }
+ }
+
+ particleRouteLength = 0;
+ Generation = 0;
+ millisLastFrame = millis();
+ RouteStep = 0;
+ VoronoiCalculated = false;
+ cellsCalculated = 0;
+ vorPointsAdded = 0;
+ voronoi = new Voronoi(); // Erase mesh
+ TempShowCells = true;
+ FileModeTSP = false;
+}
+
+public void setup()
+{
+
+ borderWidth = 6;
+
+ mainwidth = 800;
+ mainheight = 600;
+ ctrlheight = 110;
+
+ size(mainwidth, mainheight + ctrlheight, JAVA2D);
+
+ gfx = new ToxiclibsSupport(this);
+
+
+ lowBorderX = borderWidth; //mainwidth*0.01;
+ hiBorderX = mainwidth - borderWidth; //mainwidth*0.98;
+ lowBorderY = borderWidth; // mainheight*0.01;
+ hiBorderY = mainheight - borderWidth; //mainheight*0.98;
+
+ int innerWidth = mainwidth - 2 * borderWidth;
+ int innerHeight = mainheight - 2 * borderWidth;
+
+ clip=new SutherlandHodgemanClipper(new Rect(lowBorderX, lowBorderY, innerWidth, innerHeight));
+
+ MainArraysetup(); // Main particle array setup
+
+ frameRate(24);
+
+ smooth();
+ noStroke();
+ fill(153); // Background fill color, for control section
+
+ textFont(createFont("SansSerif", 10));
+
+
+ cp5 = new ControlP5(this);
+
+ int leftcolumwidth = 225;
+
+ int GUItop = mainheight + 15;
+ int GUI2ndRow = 4; // Spacing for firt row after group heading
+ int GuiRowSpacing = 14; // Spacing for subsequent rows
+ int GUIFudge = mainheight + 19; // I wish that we didn't need ONE MORE of these stupid spacings.
+
+
+ ControlGroup l3 = cp5.addGroup("Primary controls (Changing will restart)", 10, GUItop, 225);
+
+ cp5.addSlider("Stipples", 10, 10000, maxParticles, 10, GUI2ndRow, 150, 10).setGroup(l3);
+
+ InvertOnOff = cp5.addButton("INVERT_IMG", 10, 10, GUI2ndRow + GuiRowSpacing, 190, 10).setGroup(l3);
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+
+
+ Button LoadButton = cp5.addButton("LOAD_FILE", 10, 10, GUIFudge + 3*GuiRowSpacing, 175, 10);
+ LoadButton.setCaptionLabel("LOAD IMAGE FILE (.PNG, .JPG, or .GIF)");
+
+ cp5.addButton("QUIT", 10, 205, GUIFudge + 3*GuiRowSpacing, 30, 10);
+
+ cp5.addButton("SAVE_STIPPLES", 10, 25, GUIFudge + 4*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_STIPPLES").setCaptionLabel("Save Stipple File (.SVG format)");
+
+ cp5.addButton("SAVE_PATH", 10, 25, GUIFudge + 5*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_PATH").setCaptionLabel("Save \"TSP\" Path (.SVG format)");
+
+
+ ControlGroup l5 = cp5.addGroup("Display Options - Updated on next generation", leftcolumwidth+50, GUItop, 225);
+
+ cp5.addSlider("Min_Dot_Size", .5f, 8, 2, 10, 4, 140, 10).setGroup(l5);
+ cp5.controller("Min_Dot_Size").setValue(MinDotSize);
+ cp5.controller("Min_Dot_Size").setCaptionLabel("Min. Dot Size");
+
+ cp5.addSlider("Dot_Size_Range", 0, 20, 5, 10, 18, 140, 10).setGroup(l5);
+ cp5.controller("Dot_Size_Range").setValue(DotSizeFactor);
+ cp5.controller("Dot_Size_Range").setCaptionLabel("Dot Size Range");
+
+ cp5.addSlider("White_Cutoff", 0, 1, 0, 10, 32, 140, 10).setGroup(l5);
+ cp5.controller("White_Cutoff").setValue(cutoff);
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+
+
+ ImgOnOff = cp5.addButton("IMG_ON_OFF", 10, 10, 46, 90, 10);
+ ImgOnOff.setGroup(l5);
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+
+ CellOnOff = cp5.addButton("CELLS_ON_OFF", 10, 110, 46, 90, 10);
+ CellOnOff.setGroup(l5);
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+
+ PauseButton = cp5.addButton("Pause", 10, 10, 60, 190, 10);
+ PauseButton.setGroup(l5);
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+
+ OrderOnOff = cp5.addButton("ORDER_ON_OFF", 10, 10, 74, 190, 10);
+ OrderOnOff.setGroup(l5);
+ OrderOnOff.setCaptionLabel("Plotting path >> shown while paused");
+
+
+
+
+
+ TextColumnStart = 2 * leftcolumwidth + 100;
+
+ MaxDotSize = MinDotSize * (1 + DotSizeFactor);
+
+ ReInitiallizeArray = false;
+ pausemode = false;
+ showBG = false;
+ invertImg = false;
+ showPath = true;
+ showCells = false;
+ fileLoaded = false;
+ SaveNow = 0;
+}
+
+
+public void LOAD_FILE(float theValue) {
+ println(":::LOAD JPG, GIF or PNG FILE:::");
+
+ String loadPath = selectInput(); // Opens file chooser
+ if (loadPath == null) {
+ // If a file was not selected
+ println("No file was selected...");
+ }
+ else {
+ // If a file was selected, print path to file
+ println("Loaded file: " + loadPath);
+
+
+ String[] p = splitTokens(loadPath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("GIF"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("gif"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("JPG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("jpg"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("TGA"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("tga"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("PNG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("png"))
+ fileOK = true;
+
+ println("File OK: " + fileOK);
+
+ if (fileOK) {
+ imgload = loadImage(loadPath);
+ fileLoaded = true;
+ // MainArraysetup();
+ ReInitiallizeArray = true;
+ }
+ else {
+ // Can't load file
+ ErrorDisplay = "ERROR: BAD FILE TYPE";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+} //End Load File
+
+
+
+public void SAVE_PATH(float theValue) {
+ FileModeTSP = true;
+ SAVE_SVG(0);
+}
+
+
+
+public void SAVE_STIPPLES(float theValue) {
+ FileModeTSP = false;
+ SAVE_SVG(0);
+}
+
+
+
+
+
+
+public void SAVE_SVG(float theValue) {
+
+
+
+ if (pausemode != true) {
+ Pause(0.0f);
+ ErrorDisplay = "Error: PAUSE before saving.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+ savePath = selectOutput("Output .svg file name:"); // Opens file chooser
+ if (savePath == null) {
+ // If a file was not selected
+ println("No output file was selected...");
+ ErrorDisplay = "ERROR: NO FILE NAME CHOSEN.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+
+ String[] p = splitTokens(savePath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("SVG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("svg"))
+ fileOK = true;
+
+ if (fileOK == false)
+ savePath = savePath + ".svg";
+
+
+ // If a file was selected, print path to folder
+ println("Save file: " + savePath);
+ SaveNow = 1;
+ showPath = true;
+
+ ErrorDisplay = "SAVING FILE...";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+}
+
+
+
+
+public void QUIT(float theValue) {
+ exit();
+}
+
+
+public void ORDER_ON_OFF(float theValue) {
+ if (showPath) {
+ showPath = false;
+ OrderOnOff.setCaptionLabel("Plotting path >> Hide");
+ }
+ else {
+ showPath = true;
+ OrderOnOff.setCaptionLabel("Plotting path >> Shown while paused");
+ }
+}
+
+public void CELLS_ON_OFF(float theValue) {
+ if (showCells) {
+ showCells = false;
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+ }
+ else {
+ showCells = true;
+ CellOnOff.setCaptionLabel("Cells >> Show");
+ }
+}
+
+
+
+public void IMG_ON_OFF(float theValue) {
+ if (showBG) {
+ showBG = false;
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+ }
+ else {
+ showBG = true;
+ ImgOnOff.setCaptionLabel("Image BG >> Show");
+ }
+}
+
+
+public void INVERT_IMG(float theValue) {
+ if (invertImg) {
+ invertImg = false;
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+ }
+ else {
+ invertImg = true;
+ InvertOnOff.setCaptionLabel("White stipples, Black Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("Black Cutoff");
+ }
+
+ ReInitiallizeArray = true;
+ pausemode = false;
+}
+
+
+
+
+public void Pause(float theValue) {
+ // Main particle array setup (to be repeated if necessary):
+
+ if (pausemode)
+ {
+ pausemode = false;
+ println("Resuming.");
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+ }
+ else
+ {
+ pausemode = true;
+ println("Paused. Press PAUSE again to resume.");
+ PauseButton.setCaptionLabel("Paused (calculating TSP path)");
+ }
+ RouteStep = 0;
+}
+
+
+public boolean overRect(int x, int y, int width, int height)
+{
+ if (mouseX >= x && mouseX <= x+width &&
+ mouseY >= y && mouseY <= y+height) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+public void Stipples(int inValue) {
+
+ if (maxParticles != (int) inValue) {
+ println("Update: Stipple Count -> " + inValue);
+ ReInitiallizeArray = true;
+ pausemode = false;
+ }
+}
+
+
+
+
+
+public void Min_Dot_Size(float inValue) {
+ if (MinDotSize != inValue) {
+ println("Update: Min_Dot_Size -> "+inValue);
+ MinDotSize = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+public void Dot_Size_Range(float inValue) {
+ if (DotSizeFactor != inValue) {
+ println("Update: Dot Size Range -> "+inValue);
+ DotSizeFactor = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+public void White_Cutoff(float inValue) {
+ if (cutoff != inValue) {
+ println("Update: White_Cutoff -> "+inValue);
+ cutoff = inValue;
+ RouteStep = 0; // Reset TSP path
+ }
+}
+
+
+public void DoBackgrounds() {
+ if (showBG)
+ image(img, 0, 0); // Show original (cropped and scaled, but not blurred!) image in background
+ else {
+
+ if (invertImg)
+ fill(0);
+ else
+ fill(255);
+
+ rect(0, 0, mainwidth, mainheight);
+ }
+}
+
+public void OptimizePlotPath()
+{
+ int temp;
+ // Calculate and show "optimized" plotting path, beneath points.
+
+ StatusDisplay = "Optimizing plotting path";
+ /*
+ if (RouteStep % 100 == 0) {
+ println("RouteStep:" + RouteStep);
+ println("fps = " + frameRate );
+ }
+ */
+
+ Vec2D p1;
+
+
+ if (RouteStep == 0)
+ {
+
+ float cutoffScaled = 1 - cutoff;
+ // Begin process of optimizing plotting route, by flagging particles that will be shown.
+
+ particleRouteLength = 0;
+
+ boolean particleRouteTemp[] = new boolean[maxParticles];
+
+ for (int i = 0; i < maxParticles; ++i) {
+
+ particleRouteTemp[i] = false;
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+
+ if (v < cutoffScaled) {
+ particleRouteTemp[i] = true;
+ particleRouteLength++;
+ }
+ }
+
+ particleRoute = new int[particleRouteLength];
+ int tempCounter = 0;
+ for (int i = 0; i < maxParticles; ++i) {
+
+ if (particleRouteTemp[i])
+ {
+ particleRoute[tempCounter] = i;
+ tempCounter++;
+ }
+ }
+ // These are the ONLY points to be drawn in the tour.
+ }
+
+ if (RouteStep < (particleRouteLength - 2))
+ {
+
+ // Nearest neighbor ("Simple, Greedy") algorithm path optimization:
+
+ int StopPoint = RouteStep + 1000; // 1000 steps per frame displayed; you can edit this number!
+
+ if (StopPoint > (particleRouteLength - 1))
+ StopPoint = particleRouteLength - 1;
+
+ for (int i = RouteStep; i < StopPoint; ++i) {
+
+ p1 = particles[particleRoute[RouteStep]];
+ int ClosestParticle = 0;
+ float distMin = Float.MAX_VALUE;
+
+ for (int j = RouteStep + 1; j < (particleRouteLength - 1); ++j) {
+ Vec2D p2 = particles[particleRoute[j]];
+
+ float dx = p1.x - p2.x;
+ float dy = p1.y - p2.y;
+ float distance = (float) (dx*dx+dy*dy); // Only looking for closest; do not need sqrt factor!
+
+ if (distance < distMin) {
+ ClosestParticle = j;
+ distMin = distance;
+ }
+ }
+
+ temp = particleRoute[RouteStep + 1];
+ // p1 = particles[particleRoute[RouteStep + 1]];
+ particleRoute[RouteStep + 1] = particleRoute[ClosestParticle];
+ particleRoute[ClosestParticle] = temp;
+
+ if (RouteStep < (particleRouteLength - 1))
+ RouteStep++;
+ else
+ {
+ println("Now optimizing plot path" );
+ }
+ }
+ }
+ else
+ { // Initial routing is complete
+ // 2-opt heuristic optimization:
+ // Identify a pair of edges that would become shorter by reversing part of the tour.
+
+ for (int i = 0; i < 90000; ++i) { // 1000 tests per frame; you can edit this number.
+
+ int indexA = floor(random(particleRouteLength - 1));
+ int indexB = floor(random(particleRouteLength - 1));
+
+ if (Math.abs(indexA - indexB) < 2)
+ continue;
+
+ if (indexB < indexA)
+ { // swap A, B.
+ temp = indexB;
+ indexB = indexA;
+ indexA = temp;
+ }
+
+ Vec2D a0 = particles[particleRoute[indexA]];
+ Vec2D a1 = particles[particleRoute[indexA + 1]];
+ Vec2D b0 = particles[particleRoute[indexB]];
+ Vec2D b1 = particles[particleRoute[indexB + 1]];
+
+ // Original distance:
+ float dx = a0.x - a1.x;
+ float dy = a0.y - a1.y;
+ float distance = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = b0.x - b1.x;
+ dy = b0.y - b1.y;
+ distance += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ // Possible shorter distance?
+ dx = a0.x - b0.x;
+ dy = a0.y - b0.y;
+ float distance2 = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = a1.x - b1.x;
+ dy = a1.y - b1.y;
+ distance2 += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ if (distance2 < distance)
+ {
+ // Reverse tour between a1 and b0.
+
+ int indexhigh = indexB;
+ int indexlow = indexA + 1;
+
+ // println("Shorten!" + frameRate );
+
+ while (indexhigh > indexlow)
+ {
+
+ temp = particleRoute[indexlow];
+ particleRoute[indexlow] = particleRoute[indexhigh];
+ particleRoute[indexhigh] = temp;
+
+ indexhigh--;
+ indexlow++;
+ }
+ }
+ }
+ }
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+}
+
+
+
+
+
+
+
+public void doPhysics()
+{ // Iterative relaxation via weighted Lloyd's algorithm.
+
+ int temp;
+ int CountTemp;
+
+ if (VoronoiCalculated == false)
+ { // Part I: Calculate voronoi cell diagram of the points.
+
+ StatusDisplay = "Calculating Voronoi diagram ";
+
+ // float millisBaseline = millis(); // Baseline for timing studies
+ // println("Baseline. Time = " + (millis() - millisBaseline) );
+
+
+ if (vorPointsAdded == 0)
+ voronoi = new Voronoi(); // Erase mesh
+
+ temp = vorPointsAdded + 200; // This line: VoronoiPointsPerPass (Feel free to edit this number.)
+ if (temp > maxParticles)
+ temp = maxParticles;
+
+ for (int i = vorPointsAdded; i < temp; ++i) {
+
+
+ // Optional, for diagnostics:::
+ // println("particles[i].x, particles[i].y " + particles[i].x + ", " + particles[i].y );
+
+
+ //
+
+
+ voronoi.addPoint(new Vec2D(particles[i].x, particles[i].y ));
+ vorPointsAdded++;
+ }
+
+ if (vorPointsAdded >= maxParticles)
+ {
+
+ // println("Points added. Time = " + (millis() - millisBaseline) );
+
+ cellsTotal = (voronoi.getRegions().size());
+ vorPointsAdded = 0;
+ cellsCalculated = 0;
+ cellsCalculatedLast = 0;
+
+ RegionList = new Polygon2D[cellsTotal];
+
+ int i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ RegionList[i++] = poly; // Build array of polygons
+ }
+ VoronoiCalculated = true;
+ }
+ }
+ else
+ { // Part II: Calculate weighted centroids of cells.
+ // float millisBaseline = millis();
+ // println("fps = " + frameRate );
+
+ StatusDisplay = "Calculating weighted centroids";
+
+
+ temp = cellsCalculated + 100; // This line: CentroidsPerPass (Feel free to edit this number.)
+ // Higher values give slightly faster computation, but a less responsive GUI.
+
+
+ if (temp > cellsTotal)
+ {
+ temp = cellsTotal;
+ }
+
+ for (int i=cellsCalculated; i< temp; i++) {
+
+ float xMax = 0;
+ float xMin = mainwidth;
+ float yMax = 0;
+ float yMin = mainheight;
+ float xt, yt;
+
+ Polygon2D region = clip.clipPolygon(RegionList[i]);
+
+
+ for (Vec2D v : region.vertices) {
+
+ xt = v.x;
+ yt = v.y;
+
+ if (xt < xMin)
+ xMin = xt;
+ if (xt > xMax)
+ xMax = xt;
+ if (yt < yMin)
+ yMin = yt;
+ if (yt > yMax)
+ yMax = yt;
+ }
+
+ float xDiff = xMax - xMin;
+ float yDiff = yMax - yMin;
+ float maxSize = max(xDiff, yDiff);
+ float minSize = min(xDiff, yDiff);
+
+ float scaleFactor = 1.0f;
+
+ // Maximum voronoi cell extent should be between
+ // cellBuffer/2 and cellBuffer in size.
+
+ while (maxSize > cellBuffer)
+ {
+ scaleFactor *= 0.5f;
+ maxSize *= 0.5f;
+ }
+
+ while (maxSize < (cellBuffer/2))
+ {
+ scaleFactor *= 2;
+ maxSize *= 2;
+ }
+
+ if ((minSize * scaleFactor) > (cellBuffer/2))
+ { // Special correction for objects of near-unity (square-like) aspect ratio,
+ // which have larger area *and* where it is less essential to find the exact centroid:
+ scaleFactor *= 0.5f;
+ }
+
+ float StepSize = (1/scaleFactor);
+
+ float xSum = 0;
+ float ySum = 0;
+ float dSum = 0;
+ float PicDensity = 1.0f;
+
+
+ if (invertImg)
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 0.001f + (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+ else
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 255.001f - (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+
+ if (dSum > 0)
+ {
+ xSum /= dSum;
+ ySum /= dSum;
+ }
+
+ Vec2D centr;
+
+
+ float xTemp = (xSum);
+ float yTemp = (ySum);
+
+
+ if ((xTemp <= lowBorderX) || (xTemp >= hiBorderX) || (yTemp <= lowBorderY) || (yTemp >= hiBorderY)) {
+ // If new centroid is computed to be outside the visible region, use the geometric centroid instead.
+ // This will help to prevent runaway points due to numerical artifacts.
+ centr = region.getCentroid();
+ xTemp = centr.x;
+ yTemp = centr.y;
+
+ // Enforce sides, if absolutely necessary: (Failure to do so *will* cause a crash, eventually.)
+
+ if (xTemp <= lowBorderX)
+ xTemp = lowBorderX + 1;
+ if (xTemp >= hiBorderX)
+ xTemp = hiBorderX - 1;
+ if (yTemp <= lowBorderY)
+ yTemp = lowBorderY + 1;
+ if (yTemp >= hiBorderY)
+ yTemp = hiBorderY - 1;
+ }
+
+ particles[i].x = xTemp;
+ particles[i].y = yTemp;
+
+ cellsCalculated++;
+ }
+
+
+ // println("cellsCalculated = " + cellsCalculated );
+ // println("cellsTotal = " + cellsTotal );
+
+ if (cellsCalculated >= cellsTotal)
+ {
+ VoronoiCalculated = false;
+ Generation++;
+ println("Generation = " + Generation );
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+ }
+ }
+}
+
+
+public void draw()
+{
+
+ int i = 0;
+ int temp;
+ float dotScale = (MaxDotSize - MinDotSize);
+ float cutoffScaled = 1 - cutoff;
+
+ if (ReInitiallizeArray) {
+ maxParticles = (int) cp5.controller("Stipples").value(); // Only change this here!
+
+ MainArraysetup();
+ ReInitiallizeArray = false;
+ }
+
+ if (pausemode && (VoronoiCalculated == false))
+ OptimizePlotPath();
+ else
+ doPhysics();
+
+
+ if (pausemode)
+ {
+
+ DoBackgrounds();
+
+ // Draw paths:
+
+ if ( showPath ) {
+
+ stroke(128, 128, 255); // Stroke color (blue)
+ strokeWeight (1);
+
+ for ( i = 0; i < (particleRouteLength - 1); ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+ Vec2D p2 = particles[particleRoute[i + 1]];
+
+ line(p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+ // Only show "routed" particles-- those above the white cutoff.
+
+ Vec2D p1 = particles[particleRoute[i]];
+ int px = (int) p1.x;
+ int py = (int) p1.y;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ else
+ { // NOT in pause mode. i.e., just displaying stipples.
+ if (cellsCalculated == 0) {
+
+ DoBackgrounds();
+
+ if (Generation == 0)
+ {
+ TempShowCells = true;
+ }
+
+ if (showCells || TempShowCells) { // Draw voronoi cells, over background.
+ strokeWeight(1);
+ noFill();
+
+
+ if (invertImg && (showBG == false)) // TODO -- if invertImg AND NOT background
+ stroke(100);
+ else
+ stroke(200);
+
+ // stroke(200);
+
+ i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ //RegionList[i++] = poly;
+ gfx.polygon2D(clip.clipPolygon(poly));
+ }
+ }
+
+ if (showCells) {
+ // Show "before and after" centroids, when polygons are shown.
+
+ strokeWeight (MinDotSize); // Normal w/ Min & Max dot size
+ for ( i = 0; i < maxParticles; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ //Uncomment the following four lines, if you wish to display the "before" dots at weighted sizes.
+ //float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+ //if (invertImg)
+ //v = 1 - v;
+ //strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+ }
+ else {
+ // Stipple calculation is still underway
+
+ if (TempShowCells)
+ {
+ DoBackgrounds();
+ TempShowCells = false;
+ }
+
+
+ // stroke(0); // Stroke color
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = cellsCalculatedLast; i < cellsCalculated; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ if (v < cutoffScaled) {
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+
+ cellsCalculatedLast = cellsCalculated;
+ }
+ }
+
+ noStroke();
+ fill(100); // Background fill color
+ rect(0, mainheight, mainwidth, height); // Control area fill
+
+ // Underlay for hyperlink:
+ if (overRect(TextColumnStart - 10, mainheight + 35, 205, 20) )
+ {
+ fill(150);
+ rect(TextColumnStart - 10, mainheight + 35, 205, 20);
+ }
+
+ fill(255); // Text color
+
+ text("StippleGen 2 (v. 2.02)", TextColumnStart, mainheight + 15);
+ text("by Evil Mad Scientist Laboratories", TextColumnStart, mainheight + 30);
+ text("www.evilmadscientist.com/go/stipple2", TextColumnStart, mainheight + 50);
+
+ text("Generations completed: " + Generation, TextColumnStart, mainheight + 85);
+ text("Time/Frame: " + frameTime + " s", TextColumnStart, mainheight + 100);
+
+
+ if (ErrorDisp)
+ {
+ fill(255, 0, 0); // Text color
+ text(ErrorDisplay, TextColumnStart, mainheight + 70);
+ if ((millis() - ErrorTime) > 8000)
+ ErrorDisp = false;
+ }
+ else
+ text("Status: " + StatusDisplay, TextColumnStart, mainheight + 70);
+
+
+
+ if (SaveNow > 0) {
+
+ StatusDisplay = "Saving SVG File";
+ SaveNow = 0;
+
+ FileOutput = loadStrings("header.txt");
+
+ String rowTemp;
+
+ float SVGscale = (800.0f / (float) mainheight);
+ int xOffset = (int) (1600 - (SVGscale * mainwidth / 2));
+ int yOffset = (int) (400 - (SVGscale * mainheight / 2));
+
+
+ if (FileModeTSP)
+ { // Plot the PATH between the points only.
+
+ println("Save TSP File (SVG)");
+
+ // Path header::
+ rowTemp = ""); // End path description
+ }
+ else {
+ println("Save Stipple File (SVG)");
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+
+ int px = floor(p1.x);
+ int py = floor(p1.y);
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ float dotrad = (MaxDotSize - v * dotScale)/2;
+
+ float xTemp = SVGscale*p1.x + xOffset;
+ float yTemp = SVGscale*p1.y + yOffset;
+
+ rowTemp = "";
+
+ // Typ:
+
+ FileOutput = append(FileOutput, rowTemp);
+ }
+ }
+
+
+
+ // SVG footer:
+ FileOutput = append(FileOutput, "");
+ saveStrings(savePath, FileOutput);
+ FileModeTSP = false; // reset for next time
+
+ if (FileModeTSP)
+ ErrorDisplay = "TSP Path .SVG file Saved";
+ else
+ ErrorDisplay = "Stipple .SVG file saved ";
+
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+}
+
+
+
+public void mousePressed() {
+
+ // rect(TextColumnStart, mainheight, 200, 75);
+
+ if (overRect(TextColumnStart - 15, mainheight + 35, 205, 20) )
+ link("http://www.evilmadscientist.com/go/stipple2");
+}
+
+
+
+
+public void keyPressed() {
+ if (key == 'x')
+ { // If this program doesn't run slowly enough for you,
+ // simply press the 'x' key on your keyboard. :)
+ cp5.controller("Stipples").setMax(50000.0f);
+ }
+}
+
+ static public void main(String args[]) {
+ PApplet.main(new String[] { "--bgcolor=#c0c0c0", "StippleGen_2" });
+ }
+}
diff --git a/StippleGen_2.02/StippleGen_2/application.windows/source/StippleGen_2.pde b/StippleGen_2.02/StippleGen_2/application.windows/source/StippleGen_2.pde
new file mode 100644
index 0000000..40c0771
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/application.windows/source/StippleGen_2.pde
@@ -0,0 +1,1373 @@
+/**
+
+ StippleGen_2
+
+ 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
+
+ *******************************************************************************
+
+ Change Log:
+
+ v 2.02
+ * Force files to end in .svg
+ * Fix bug that gave wrong size to stipple files saved white stipples on black background
+
+ v 2.01:
+ * Improved handling of Save process, to prevent accidental "not saving" by users.
+
+ v 2.0:
+ * Add tone reversal option (white on black / black on white)
+ * Reduce vertical extent of GUI, to reduce likelihood of cropping on small screens
+ * Speling corections
+ * Fixed a bug that caused unintended cropping of long, wide images
+ * Reorganized GUI controls
+ * Fail less disgracefully when a bad image type is selected.
+
+ *******************************************************************************
+
+
+
+ Program is based on the Toxic Libs Library ( http://toxiclibs.org/ )
+ & example code:
+ http://forum.processing.org/topic/toxiclib-voronoi-example-sketch
+
+
+ Additional inspiration:
+ Stipple Cam from Jim Bumgardner
+ http://joyofprocessing.com/blog/2011/11/stipple-cam/
+
+ and
+
+ MeshLibDemo.pde - Demo of Lee Byron's Mesh library, by
+ Marius Watz - http://workshop.evolutionzone.com/
+
+
+ Requires ControlP5 library and Toxic Libs library:
+ http://www.sojamo.de/libraries/controlP5/
+ http://hg.postspectacular.com/toxiclibs/downloads
+
+
+ */
+
+
+/*
+ *
+ * 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
+ */
+
+
+// You need the controlP5 library from http://www.sojamo.de/libraries/controlP5/
+import controlP5.*;
+
+//You need the Toxic Libs library: http://hg.postspectacular.com/toxiclibs/downloads
+
+import toxi.geom.*;
+import toxi.geom.mesh2d.*;
+import toxi.util.datatypes.*;
+import toxi.processing.*;
+
+// helper class for rendering
+ToxiclibsSupport gfx;
+
+import javax.swing.UIManager;
+import javax.swing.JFileChooser;
+
+
+
+
+// Feel free to play with these three default settings:
+int maxParticles = 2000; // Max value is normally 10000. Press 'x' key to allow 50000 stipples. (SLOW)
+float MinDotSize = 1.75; //2;
+float DotSizeFactor = 4; //5;
+float cutoff = 0; // White cutoff value
+
+
+int cellBuffer = 100; //Scale each cell to fit in a cellBuffer-sized square window for computing the centroid.
+
+
+// Display window and GUI area sizes:
+int mainwidth;
+int mainheight;
+int borderWidth;
+int ctrlheight;
+int TextColumnStart;
+
+
+
+float lowBorderX;
+float hiBorderX;
+float lowBorderY;
+float hiBorderY;
+
+
+
+float MaxDotSize;
+boolean ReInitiallizeArray;
+boolean pausemode;
+boolean fileLoaded;
+int SaveNow;
+String savePath;
+String[] FileOutput;
+
+
+
+
+String StatusDisplay = "Initializing, please wait. :)";
+float millisLastFrame = 0;
+float frameTime = 0;
+
+String ErrorDisplay = "";
+float ErrorTime;
+Boolean ErrorDisp = false;
+
+
+int Generation;
+int particleRouteLength;
+int RouteStep;
+
+boolean showBG;
+boolean showPath;
+boolean showCells;
+boolean invertImg;
+boolean TempShowCells;
+boolean FileModeTSP;
+
+int vorPointsAdded;
+boolean VoronoiCalculated;
+
+// Toxic libs library setup:
+Voronoi voronoi;
+Polygon2D RegionList[];
+
+PolygonClipper2D clip; // polygon clipper
+
+int cellsTotal, cellsCalculated, cellsCalculatedLast;
+
+
+// ControlP5 GUI library variables setup
+Textlabel ProgName;
+Button OrderOnOff, ImgOnOff, CellOnOff, InvertOnOff, PauseButton;
+ControlP5 cp5;
+
+
+PImage img, imgload, imgblur;
+
+Vec2D[] particles;
+int[] particleRoute;
+
+
+
+void LoadImageAndScale() {
+
+ int tempx = 0;
+ int tempy = 0;
+
+ img = createImage(mainwidth, mainheight, RGB);
+ imgblur = createImage(mainwidth, mainheight, RGB);
+
+ img.loadPixels();
+
+ if (invertImg)
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(0);
+ }
+ else
+ for (int i = 0; i < img.pixels.length; i++) {
+ img.pixels[i] = color(255);
+ }
+
+ img.updatePixels();
+
+ if ( fileLoaded == false) {
+ // Load a demo image, at least until we have a "real" image to work with.
+
+ imgload = loadImage("grace.jpg"); // Load demo image
+ // Image source: http://commons.wikimedia.org/wiki/File:Kelly,_Grace_(Rear_Window).jpg
+ }
+
+ if ((imgload.width > mainwidth) || (imgload.height > mainheight)) {
+
+ if (((float) imgload.width / (float)imgload.height) > ((float) mainwidth / (float) mainheight))
+ {
+ imgload.resize(mainwidth, 0);
+ }
+ else
+ {
+ imgload.resize(0, mainheight);
+ }
+ }
+
+ if (imgload.height < (mainheight - 2) ) {
+ tempy = (int) (( mainheight - imgload.height ) / 2) ;
+ }
+ if (imgload.width < (mainwidth - 2)) {
+ tempx = (int) (( mainwidth - imgload.width ) / 2) ;
+ }
+
+ img.copy(imgload, 0, 0, imgload.width, imgload.height, tempx, tempy, imgload.width, imgload.height);
+ // For background image!
+
+
+ /*
+ // Optional gamma correction for background image.
+ img.loadPixels();
+
+ float tempFloat;
+ float GammaValue = 1.0; // Normally in the range 0.25 - 4.0
+
+ for (int i = 0; i < img.pixels.length; i++) {
+ tempFloat = brightness(img.pixels[i])/255;
+ img.pixels[i] = color(floor(255 * pow(tempFloat,GammaValue)));
+ }
+ img.updatePixels();
+ */
+
+
+ imgblur.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
+ // This is a duplicate of the background image, that we will apply a blur to,
+ // to reduce "high frequency" noise artifacts.
+
+ imgblur.filter(BLUR, 1); // Low-level blur filter to elminate pixel-to-pixel noise artifacts.
+ imgblur.loadPixels();
+}
+
+
+void MainArraysetup() {
+ // Main particle array initialization (to be called whenever necessary):
+
+ LoadImageAndScale();
+
+ // image(img, 0, 0); // SHOW BG IMG
+
+ particles = new Vec2D[maxParticles];
+
+
+ // Fill array by "rejection sampling"
+ int i = 0;
+ while (i < maxParticles)
+ {
+
+ float fx = lowBorderX + random(hiBorderX - lowBorderX);
+ float fy = lowBorderY + random(hiBorderY - lowBorderY);
+
+ float p = brightness(imgblur.pixels[ floor(fy)*imgblur.width + floor(fx) ])/255;
+ // OK to use simple floor_ rounding here, because this is a one-time operation,
+ // creating the initial distribution that will be iterated.
+
+ if (invertImg)
+ {
+ p = 1 - p;
+ }
+
+ if (random(1) >= p ) {
+ Vec2D p1 = new Vec2D(fx, fy);
+ particles[i] = p1;
+ i++;
+ }
+ }
+
+ particleRouteLength = 0;
+ Generation = 0;
+ millisLastFrame = millis();
+ RouteStep = 0;
+ VoronoiCalculated = false;
+ cellsCalculated = 0;
+ vorPointsAdded = 0;
+ voronoi = new Voronoi(); // Erase mesh
+ TempShowCells = true;
+ FileModeTSP = false;
+}
+
+void setup()
+{
+
+ borderWidth = 6;
+
+ mainwidth = 800;
+ mainheight = 600;
+ ctrlheight = 110;
+
+ size(mainwidth, mainheight + ctrlheight, JAVA2D);
+
+ gfx = new ToxiclibsSupport(this);
+
+
+ lowBorderX = borderWidth; //mainwidth*0.01;
+ hiBorderX = mainwidth - borderWidth; //mainwidth*0.98;
+ lowBorderY = borderWidth; // mainheight*0.01;
+ hiBorderY = mainheight - borderWidth; //mainheight*0.98;
+
+ int innerWidth = mainwidth - 2 * borderWidth;
+ int innerHeight = mainheight - 2 * borderWidth;
+
+ clip=new SutherlandHodgemanClipper(new Rect(lowBorderX, lowBorderY, innerWidth, innerHeight));
+
+ MainArraysetup(); // Main particle array setup
+
+ frameRate(24);
+
+ smooth();
+ noStroke();
+ fill(153); // Background fill color, for control section
+
+ textFont(createFont("SansSerif", 10));
+
+
+ cp5 = new ControlP5(this);
+
+ int leftcolumwidth = 225;
+
+ int GUItop = mainheight + 15;
+ int GUI2ndRow = 4; // Spacing for firt row after group heading
+ int GuiRowSpacing = 14; // Spacing for subsequent rows
+ int GUIFudge = mainheight + 19; // I wish that we didn't need ONE MORE of these stupid spacings.
+
+
+ ControlGroup l3 = cp5.addGroup("Primary controls (Changing will restart)", 10, GUItop, 225);
+
+ cp5.addSlider("Stipples", 10, 10000, maxParticles, 10, GUI2ndRow, 150, 10).setGroup(l3);
+
+ InvertOnOff = cp5.addButton("INVERT_IMG", 10, 10, GUI2ndRow + GuiRowSpacing, 190, 10).setGroup(l3);
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+
+
+ Button LoadButton = cp5.addButton("LOAD_FILE", 10, 10, GUIFudge + 3*GuiRowSpacing, 175, 10);
+ LoadButton.setCaptionLabel("LOAD IMAGE FILE (.PNG, .JPG, or .GIF)");
+
+ cp5.addButton("QUIT", 10, 205, GUIFudge + 3*GuiRowSpacing, 30, 10);
+
+ cp5.addButton("SAVE_STIPPLES", 10, 25, GUIFudge + 4*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_STIPPLES").setCaptionLabel("Save Stipple File (.SVG format)");
+
+ cp5.addButton("SAVE_PATH", 10, 25, GUIFudge + 5*GuiRowSpacing, 160, 10);
+ cp5.controller("SAVE_PATH").setCaptionLabel("Save \"TSP\" Path (.SVG format)");
+
+
+ ControlGroup l5 = cp5.addGroup("Display Options - Updated on next generation", leftcolumwidth+50, GUItop, 225);
+
+ cp5.addSlider("Min_Dot_Size", .5, 8, 2, 10, 4, 140, 10).setGroup(l5);
+ cp5.controller("Min_Dot_Size").setValue(MinDotSize);
+ cp5.controller("Min_Dot_Size").setCaptionLabel("Min. Dot Size");
+
+ cp5.addSlider("Dot_Size_Range", 0, 20, 5, 10, 18, 140, 10).setGroup(l5);
+ cp5.controller("Dot_Size_Range").setValue(DotSizeFactor);
+ cp5.controller("Dot_Size_Range").setCaptionLabel("Dot Size Range");
+
+ cp5.addSlider("White_Cutoff", 0, 1, 0, 10, 32, 140, 10).setGroup(l5);
+ cp5.controller("White_Cutoff").setValue(cutoff);
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+
+
+ ImgOnOff = cp5.addButton("IMG_ON_OFF", 10, 10, 46, 90, 10);
+ ImgOnOff.setGroup(l5);
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+
+ CellOnOff = cp5.addButton("CELLS_ON_OFF", 10, 110, 46, 90, 10);
+ CellOnOff.setGroup(l5);
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+
+ PauseButton = cp5.addButton("Pause", 10, 10, 60, 190, 10);
+ PauseButton.setGroup(l5);
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+
+ OrderOnOff = cp5.addButton("ORDER_ON_OFF", 10, 10, 74, 190, 10);
+ OrderOnOff.setGroup(l5);
+ OrderOnOff.setCaptionLabel("Plotting path >> shown while paused");
+
+
+
+
+
+ TextColumnStart = 2 * leftcolumwidth + 100;
+
+ MaxDotSize = MinDotSize * (1 + DotSizeFactor);
+
+ ReInitiallizeArray = false;
+ pausemode = false;
+ showBG = false;
+ invertImg = false;
+ showPath = true;
+ showCells = false;
+ fileLoaded = false;
+ SaveNow = 0;
+}
+
+
+void LOAD_FILE(float theValue) {
+ println(":::LOAD JPG, GIF or PNG FILE:::");
+
+ String loadPath = selectInput(); // Opens file chooser
+ if (loadPath == null) {
+ // If a file was not selected
+ println("No file was selected...");
+ }
+ else {
+ // If a file was selected, print path to file
+ println("Loaded file: " + loadPath);
+
+
+ String[] p = splitTokens(loadPath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("GIF"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("gif"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("JPG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("jpg"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("TGA"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("tga"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("PNG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("png"))
+ fileOK = true;
+
+ println("File OK: " + fileOK);
+
+ if (fileOK) {
+ imgload = loadImage(loadPath);
+ fileLoaded = true;
+ // MainArraysetup();
+ ReInitiallizeArray = true;
+ }
+ else {
+ // Can't load file
+ ErrorDisplay = "ERROR: BAD FILE TYPE";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+} //End Load File
+
+
+
+void SAVE_PATH(float theValue) {
+ FileModeTSP = true;
+ SAVE_SVG(0);
+}
+
+
+
+void SAVE_STIPPLES(float theValue) {
+ FileModeTSP = false;
+ SAVE_SVG(0);
+}
+
+
+
+
+
+
+void SAVE_SVG(float theValue) {
+
+
+
+ if (pausemode != true) {
+ Pause(0.0);
+ ErrorDisplay = "Error: PAUSE before saving.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+ savePath = selectOutput("Output .svg file name:"); // Opens file chooser
+ if (savePath == null) {
+ // If a file was not selected
+ println("No output file was selected...");
+ ErrorDisplay = "ERROR: NO FILE NAME CHOSEN.";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ else {
+
+ String[] p = splitTokens(savePath, ".");
+ boolean fileOK = false;
+
+ if ( p[p.length - 1].equals("SVG"))
+ fileOK = true;
+ if ( p[p.length - 1].equals("svg"))
+ fileOK = true;
+
+ if (fileOK == false)
+ savePath = savePath + ".svg";
+
+
+ // If a file was selected, print path to folder
+ println("Save file: " + savePath);
+ SaveNow = 1;
+ showPath = true;
+
+ ErrorDisplay = "SAVING FILE...";
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+ }
+}
+
+
+
+
+void QUIT(float theValue) {
+ exit();
+}
+
+
+void ORDER_ON_OFF(float theValue) {
+ if (showPath) {
+ showPath = false;
+ OrderOnOff.setCaptionLabel("Plotting path >> Hide");
+ }
+ else {
+ showPath = true;
+ OrderOnOff.setCaptionLabel("Plotting path >> Shown while paused");
+ }
+}
+
+void CELLS_ON_OFF(float theValue) {
+ if (showCells) {
+ showCells = false;
+ CellOnOff.setCaptionLabel("Cells >> Hide");
+ }
+ else {
+ showCells = true;
+ CellOnOff.setCaptionLabel("Cells >> Show");
+ }
+}
+
+
+
+void IMG_ON_OFF(float theValue) {
+ if (showBG) {
+ showBG = false;
+ ImgOnOff.setCaptionLabel("Image BG >> Hide");
+ }
+ else {
+ showBG = true;
+ ImgOnOff.setCaptionLabel("Image BG >> Show");
+ }
+}
+
+
+void INVERT_IMG(float theValue) {
+ if (invertImg) {
+ invertImg = false;
+ InvertOnOff.setCaptionLabel("Black stipples, White Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("White Cutoff");
+ }
+ else {
+ invertImg = true;
+ InvertOnOff.setCaptionLabel("White stipples, Black Background");
+ cp5.controller("White_Cutoff").setCaptionLabel("Black Cutoff");
+ }
+
+ ReInitiallizeArray = true;
+ pausemode = false;
+}
+
+
+
+
+void Pause(float theValue) {
+ // Main particle array setup (to be repeated if necessary):
+
+ if (pausemode)
+ {
+ pausemode = false;
+ println("Resuming.");
+ PauseButton.setCaptionLabel("Pause (to calculate TSP path)");
+ }
+ else
+ {
+ pausemode = true;
+ println("Paused. Press PAUSE again to resume.");
+ PauseButton.setCaptionLabel("Paused (calculating TSP path)");
+ }
+ RouteStep = 0;
+}
+
+
+boolean overRect(int x, int y, int width, int height)
+{
+ if (mouseX >= x && mouseX <= x+width &&
+ mouseY >= y && mouseY <= y+height) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+void Stipples(int inValue) {
+
+ if (maxParticles != (int) inValue) {
+ println("Update: Stipple Count -> " + inValue);
+ ReInitiallizeArray = true;
+ pausemode = false;
+ }
+}
+
+
+
+
+
+void Min_Dot_Size(float inValue) {
+ if (MinDotSize != inValue) {
+ println("Update: Min_Dot_Size -> "+inValue);
+ MinDotSize = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void Dot_Size_Range(float inValue) {
+ if (DotSizeFactor != inValue) {
+ println("Update: Dot Size Range -> "+inValue);
+ DotSizeFactor = inValue;
+ MaxDotSize = MinDotSize* (1 + DotSizeFactor);
+ }
+}
+
+
+void White_Cutoff(float inValue) {
+ if (cutoff != inValue) {
+ println("Update: White_Cutoff -> "+inValue);
+ cutoff = inValue;
+ RouteStep = 0; // Reset TSP path
+ }
+}
+
+
+void DoBackgrounds() {
+ if (showBG)
+ image(img, 0, 0); // Show original (cropped and scaled, but not blurred!) image in background
+ else {
+
+ if (invertImg)
+ fill(0);
+ else
+ fill(255);
+
+ rect(0, 0, mainwidth, mainheight);
+ }
+}
+
+void OptimizePlotPath()
+{
+ int temp;
+ // Calculate and show "optimized" plotting path, beneath points.
+
+ StatusDisplay = "Optimizing plotting path";
+ /*
+ if (RouteStep % 100 == 0) {
+ println("RouteStep:" + RouteStep);
+ println("fps = " + frameRate );
+ }
+ */
+
+ Vec2D p1;
+
+
+ if (RouteStep == 0)
+ {
+
+ float cutoffScaled = 1 - cutoff;
+ // Begin process of optimizing plotting route, by flagging particles that will be shown.
+
+ particleRouteLength = 0;
+
+ boolean particleRouteTemp[] = new boolean[maxParticles];
+
+ for (int i = 0; i < maxParticles; ++i) {
+
+ particleRouteTemp[i] = false;
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+
+ if (v < cutoffScaled) {
+ particleRouteTemp[i] = true;
+ particleRouteLength++;
+ }
+ }
+
+ particleRoute = new int[particleRouteLength];
+ int tempCounter = 0;
+ for (int i = 0; i < maxParticles; ++i) {
+
+ if (particleRouteTemp[i])
+ {
+ particleRoute[tempCounter] = i;
+ tempCounter++;
+ }
+ }
+ // These are the ONLY points to be drawn in the tour.
+ }
+
+ if (RouteStep < (particleRouteLength - 2))
+ {
+
+ // Nearest neighbor ("Simple, Greedy") algorithm path optimization:
+
+ int StopPoint = RouteStep + 1000; // 1000 steps per frame displayed; you can edit this number!
+
+ if (StopPoint > (particleRouteLength - 1))
+ StopPoint = particleRouteLength - 1;
+
+ for (int i = RouteStep; i < StopPoint; ++i) {
+
+ p1 = particles[particleRoute[RouteStep]];
+ int ClosestParticle = 0;
+ float distMin = Float.MAX_VALUE;
+
+ for (int j = RouteStep + 1; j < (particleRouteLength - 1); ++j) {
+ Vec2D p2 = particles[particleRoute[j]];
+
+ float dx = p1.x - p2.x;
+ float dy = p1.y - p2.y;
+ float distance = (float) (dx*dx+dy*dy); // Only looking for closest; do not need sqrt factor!
+
+ if (distance < distMin) {
+ ClosestParticle = j;
+ distMin = distance;
+ }
+ }
+
+ temp = particleRoute[RouteStep + 1];
+ // p1 = particles[particleRoute[RouteStep + 1]];
+ particleRoute[RouteStep + 1] = particleRoute[ClosestParticle];
+ particleRoute[ClosestParticle] = temp;
+
+ if (RouteStep < (particleRouteLength - 1))
+ RouteStep++;
+ else
+ {
+ println("Now optimizing plot path" );
+ }
+ }
+ }
+ else
+ { // Initial routing is complete
+ // 2-opt heuristic optimization:
+ // Identify a pair of edges that would become shorter by reversing part of the tour.
+
+ for (int i = 0; i < 90000; ++i) { // 1000 tests per frame; you can edit this number.
+
+ int indexA = floor(random(particleRouteLength - 1));
+ int indexB = floor(random(particleRouteLength - 1));
+
+ if (Math.abs(indexA - indexB) < 2)
+ continue;
+
+ if (indexB < indexA)
+ { // swap A, B.
+ temp = indexB;
+ indexB = indexA;
+ indexA = temp;
+ }
+
+ Vec2D a0 = particles[particleRoute[indexA]];
+ Vec2D a1 = particles[particleRoute[indexA + 1]];
+ Vec2D b0 = particles[particleRoute[indexB]];
+ Vec2D b1 = particles[particleRoute[indexB + 1]];
+
+ // Original distance:
+ float dx = a0.x - a1.x;
+ float dy = a0.y - a1.y;
+ float distance = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = b0.x - b1.x;
+ dy = b0.y - b1.y;
+ distance += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ // Possible shorter distance?
+ dx = a0.x - b0.x;
+ dy = a0.y - b0.y;
+ float distance2 = (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+ dx = a1.x - b1.x;
+ dy = a1.y - b1.y;
+ distance2 += (float) (dx*dx+dy*dy); // Only a comparison; do not need sqrt factor!
+
+ if (distance2 < distance)
+ {
+ // Reverse tour between a1 and b0.
+
+ int indexhigh = indexB;
+ int indexlow = indexA + 1;
+
+ // println("Shorten!" + frameRate );
+
+ while (indexhigh > indexlow)
+ {
+
+ temp = particleRoute[indexlow];
+ particleRoute[indexlow] = particleRoute[indexhigh];
+ particleRoute[indexhigh] = temp;
+
+ indexhigh--;
+ indexlow++;
+ }
+ }
+ }
+ }
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+}
+
+
+
+
+
+
+
+void doPhysics()
+{ // Iterative relaxation via weighted Lloyd's algorithm.
+
+ int temp;
+ int CountTemp;
+
+ if (VoronoiCalculated == false)
+ { // Part I: Calculate voronoi cell diagram of the points.
+
+ StatusDisplay = "Calculating Voronoi diagram ";
+
+ // float millisBaseline = millis(); // Baseline for timing studies
+ // println("Baseline. Time = " + (millis() - millisBaseline) );
+
+
+ if (vorPointsAdded == 0)
+ voronoi = new Voronoi(); // Erase mesh
+
+ temp = vorPointsAdded + 200; // This line: VoronoiPointsPerPass (Feel free to edit this number.)
+ if (temp > maxParticles)
+ temp = maxParticles;
+
+ for (int i = vorPointsAdded; i < temp; ++i) {
+
+
+ // Optional, for diagnostics:::
+ // println("particles[i].x, particles[i].y " + particles[i].x + ", " + particles[i].y );
+
+
+ //
+
+
+ voronoi.addPoint(new Vec2D(particles[i].x, particles[i].y ));
+ vorPointsAdded++;
+ }
+
+ if (vorPointsAdded >= maxParticles)
+ {
+
+ // println("Points added. Time = " + (millis() - millisBaseline) );
+
+ cellsTotal = (voronoi.getRegions().size());
+ vorPointsAdded = 0;
+ cellsCalculated = 0;
+ cellsCalculatedLast = 0;
+
+ RegionList = new Polygon2D[cellsTotal];
+
+ int i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ RegionList[i++] = poly; // Build array of polygons
+ }
+ VoronoiCalculated = true;
+ }
+ }
+ else
+ { // Part II: Calculate weighted centroids of cells.
+ // float millisBaseline = millis();
+ // println("fps = " + frameRate );
+
+ StatusDisplay = "Calculating weighted centroids";
+
+
+ temp = cellsCalculated + 100; // This line: CentroidsPerPass (Feel free to edit this number.)
+ // Higher values give slightly faster computation, but a less responsive GUI.
+
+
+ if (temp > cellsTotal)
+ {
+ temp = cellsTotal;
+ }
+
+ for (int i=cellsCalculated; i< temp; i++) {
+
+ float xMax = 0;
+ float xMin = mainwidth;
+ float yMax = 0;
+ float yMin = mainheight;
+ float xt, yt;
+
+ Polygon2D region = clip.clipPolygon(RegionList[i]);
+
+
+ for (Vec2D v : region.vertices) {
+
+ xt = v.x;
+ yt = v.y;
+
+ if (xt < xMin)
+ xMin = xt;
+ if (xt > xMax)
+ xMax = xt;
+ if (yt < yMin)
+ yMin = yt;
+ if (yt > yMax)
+ yMax = yt;
+ }
+
+ float xDiff = xMax - xMin;
+ float yDiff = yMax - yMin;
+ float maxSize = max(xDiff, yDiff);
+ float minSize = min(xDiff, yDiff);
+
+ float scaleFactor = 1.0;
+
+ // Maximum voronoi cell extent should be between
+ // cellBuffer/2 and cellBuffer in size.
+
+ while (maxSize > cellBuffer)
+ {
+ scaleFactor *= 0.5;
+ maxSize *= 0.5;
+ }
+
+ while (maxSize < (cellBuffer/2))
+ {
+ scaleFactor *= 2;
+ maxSize *= 2;
+ }
+
+ if ((minSize * scaleFactor) > (cellBuffer/2))
+ { // Special correction for objects of near-unity (square-like) aspect ratio,
+ // which have larger area *and* where it is less essential to find the exact centroid:
+ scaleFactor *= 0.5;
+ }
+
+ float StepSize = (1/scaleFactor);
+
+ float xSum = 0;
+ float ySum = 0;
+ float dSum = 0;
+ float PicDensity = 1.0;
+
+
+ if (invertImg)
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 0.001 + (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+ else
+ for (float x=xMin; x<=xMax; x += StepSize) {
+ for (float y=yMin; y<=yMax; y += StepSize) {
+
+ Vec2D p0 = new Vec2D(x, y);
+ if (region.containsPoint(p0)) {
+
+ // Thanks to polygon clipping, NO vertices will be beyond the sides of imgblur.
+ PicDensity = 255.001 - (brightness(imgblur.pixels[ round(y)*imgblur.width + round(x) ]));
+
+
+ xSum += PicDensity * x;
+ ySum += PicDensity * y;
+ dSum += PicDensity;
+ }
+ }
+ }
+
+ if (dSum > 0)
+ {
+ xSum /= dSum;
+ ySum /= dSum;
+ }
+
+ Vec2D centr;
+
+
+ float xTemp = (xSum);
+ float yTemp = (ySum);
+
+
+ if ((xTemp <= lowBorderX) || (xTemp >= hiBorderX) || (yTemp <= lowBorderY) || (yTemp >= hiBorderY)) {
+ // If new centroid is computed to be outside the visible region, use the geometric centroid instead.
+ // This will help to prevent runaway points due to numerical artifacts.
+ centr = region.getCentroid();
+ xTemp = centr.x;
+ yTemp = centr.y;
+
+ // Enforce sides, if absolutely necessary: (Failure to do so *will* cause a crash, eventually.)
+
+ if (xTemp <= lowBorderX)
+ xTemp = lowBorderX + 1;
+ if (xTemp >= hiBorderX)
+ xTemp = hiBorderX - 1;
+ if (yTemp <= lowBorderY)
+ yTemp = lowBorderY + 1;
+ if (yTemp >= hiBorderY)
+ yTemp = hiBorderY - 1;
+ }
+
+ particles[i].x = xTemp;
+ particles[i].y = yTemp;
+
+ cellsCalculated++;
+ }
+
+
+ // println("cellsCalculated = " + cellsCalculated );
+ // println("cellsTotal = " + cellsTotal );
+
+ if (cellsCalculated >= cellsTotal)
+ {
+ VoronoiCalculated = false;
+ Generation++;
+ println("Generation = " + Generation );
+
+ frameTime = (millis() - millisLastFrame)/1000;
+ millisLastFrame = millis();
+ }
+ }
+}
+
+
+void draw()
+{
+
+ int i = 0;
+ int temp;
+ float dotScale = (MaxDotSize - MinDotSize);
+ float cutoffScaled = 1 - cutoff;
+
+ if (ReInitiallizeArray) {
+ maxParticles = (int) cp5.controller("Stipples").value(); // Only change this here!
+
+ MainArraysetup();
+ ReInitiallizeArray = false;
+ }
+
+ if (pausemode && (VoronoiCalculated == false))
+ OptimizePlotPath();
+ else
+ doPhysics();
+
+
+ if (pausemode)
+ {
+
+ DoBackgrounds();
+
+ // Draw paths:
+
+ if ( showPath ) {
+
+ stroke(128, 128, 255); // Stroke color (blue)
+ strokeWeight (1);
+
+ for ( i = 0; i < (particleRouteLength - 1); ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+ Vec2D p2 = particles[particleRoute[i + 1]];
+
+ line(p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+ // Only show "routed" particles-- those above the white cutoff.
+
+ Vec2D p1 = particles[particleRoute[i]];
+ int px = (int) p1.x;
+ int py = (int) p1.y;
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ else
+ { // NOT in pause mode. i.e., just displaying stipples.
+ if (cellsCalculated == 0) {
+
+ DoBackgrounds();
+
+ if (Generation == 0)
+ {
+ TempShowCells = true;
+ }
+
+ if (showCells || TempShowCells) { // Draw voronoi cells, over background.
+ strokeWeight(1);
+ noFill();
+
+
+ if (invertImg && (showBG == false)) // TODO -- if invertImg AND NOT background
+ stroke(100);
+ else
+ stroke(200);
+
+ // stroke(200);
+
+ i = 0;
+ for (Polygon2D poly : voronoi.getRegions()) {
+ //RegionList[i++] = poly;
+ gfx.polygon2D(clip.clipPolygon(poly));
+ }
+ }
+
+ if (showCells) {
+ // Show "before and after" centroids, when polygons are shown.
+
+ strokeWeight (MinDotSize); // Normal w/ Min & Max dot size
+ for ( i = 0; i < maxParticles; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ //Uncomment the following four lines, if you wish to display the "before" dots at weighted sizes.
+ //float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+ //if (invertImg)
+ //v = 1 - v;
+ //strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+ }
+ else {
+ // Stipple calculation is still underway
+
+ if (TempShowCells)
+ {
+ DoBackgrounds();
+ TempShowCells = false;
+ }
+
+
+ // stroke(0); // Stroke color
+
+
+ if (invertImg)
+ stroke(255);
+ else
+ stroke(0);
+
+ for ( i = cellsCalculatedLast; i < cellsCalculated; ++i) {
+
+ int px = (int) particles[i].x;
+ int py = (int) particles[i].y;
+
+ if ((px >= imgblur.width) || (py >= imgblur.height) || (px < 0) || (py < 0))
+ continue;
+ {
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ if (v < cutoffScaled) {
+ strokeWeight (MaxDotSize - v * dotScale);
+ point(px, py);
+ }
+ }
+ }
+
+ cellsCalculatedLast = cellsCalculated;
+ }
+ }
+
+ noStroke();
+ fill(100); // Background fill color
+ rect(0, mainheight, mainwidth, height); // Control area fill
+
+ // Underlay for hyperlink:
+ if (overRect(TextColumnStart - 10, mainheight + 35, 205, 20) )
+ {
+ fill(150);
+ rect(TextColumnStart - 10, mainheight + 35, 205, 20);
+ }
+
+ fill(255); // Text color
+
+ text("StippleGen 2 (v. 2.02)", TextColumnStart, mainheight + 15);
+ text("by Evil Mad Scientist Laboratories", TextColumnStart, mainheight + 30);
+ text("www.evilmadscientist.com/go/stipple2", TextColumnStart, mainheight + 50);
+
+ text("Generations completed: " + Generation, TextColumnStart, mainheight + 85);
+ text("Time/Frame: " + frameTime + " s", TextColumnStart, mainheight + 100);
+
+
+ if (ErrorDisp)
+ {
+ fill(255, 0, 0); // Text color
+ text(ErrorDisplay, TextColumnStart, mainheight + 70);
+ if ((millis() - ErrorTime) > 8000)
+ ErrorDisp = false;
+ }
+ else
+ text("Status: " + StatusDisplay, TextColumnStart, mainheight + 70);
+
+
+
+ if (SaveNow > 0) {
+
+ StatusDisplay = "Saving SVG File";
+ SaveNow = 0;
+
+ FileOutput = loadStrings("header.txt");
+
+ String rowTemp;
+
+ float SVGscale = (800.0 / (float) mainheight);
+ int xOffset = (int) (1600 - (SVGscale * mainwidth / 2));
+ int yOffset = (int) (400 - (SVGscale * mainheight / 2));
+
+
+ if (FileModeTSP)
+ { // Plot the PATH between the points only.
+
+ println("Save TSP File (SVG)");
+
+ // Path header::
+ rowTemp = ""); // End path description
+ }
+ else {
+ println("Save Stipple File (SVG)");
+
+ for ( i = 0; i < particleRouteLength; ++i) {
+
+ Vec2D p1 = particles[particleRoute[i]];
+
+ int px = floor(p1.x);
+ int py = floor(p1.y);
+
+ float v = (brightness(imgblur.pixels[ py*imgblur.width + px ]))/255;
+
+ if (invertImg)
+ v = 1 - v;
+
+ float dotrad = (MaxDotSize - v * dotScale)/2;
+
+ float xTemp = SVGscale*p1.x + xOffset;
+ float yTemp = SVGscale*p1.y + yOffset;
+
+ rowTemp = "";
+
+ // Typ:
+
+ FileOutput = append(FileOutput, rowTemp);
+ }
+ }
+
+
+
+ // SVG footer:
+ FileOutput = append(FileOutput, "");
+ saveStrings(savePath, FileOutput);
+ FileModeTSP = false; // reset for next time
+
+ if (FileModeTSP)
+ ErrorDisplay = "TSP Path .SVG file Saved";
+ else
+ ErrorDisplay = "Stipple .SVG file saved ";
+
+ ErrorTime = millis();
+ ErrorDisp = true;
+ }
+}
+
+
+
+void mousePressed() {
+
+ // rect(TextColumnStart, mainheight, 200, 75);
+
+ if (overRect(TextColumnStart - 15, mainheight + 35, 205, 20) )
+ link("http://www.evilmadscientist.com/go/stipple2");
+}
+
+
+
+
+void keyPressed() {
+ if (key == 'x')
+ { // If this program doesn't run slowly enough for you,
+ // simply press the 'x' key on your keyboard. :)
+ cp5.controller("Stipples").setMax(50000.0);
+ }
+}
+
diff --git a/StippleGen_2.02/StippleGen_2/data/grace.jpg b/StippleGen_2.02/StippleGen_2/data/grace.jpg
new file mode 100644
index 0000000..4083902
Binary files /dev/null and b/StippleGen_2.02/StippleGen_2/data/grace.jpg differ
diff --git a/StippleGen_2.02/StippleGen_2/data/header.txt b/StippleGen_2.02/StippleGen_2/data/header.txt
new file mode 100644
index 0000000..c7131f0
--- /dev/null
+++ b/StippleGen_2.02/StippleGen_2/data/header.txt
@@ -0,0 +1,48 @@
+
+
+
+