// TileSaver.pde - v0.12 2007.0326
// Marius Watz - http://workshop.evolutionzone.com
//
// Class for rendering high-resolution images by splitting them into
// tiles using the viewport.
class TileSaver {
public boolean isTiling=false,done=true;
public boolean doSavePreview=false;
PApplet p;
float FOV=60; // initial field of view
float cameraZ, width, height;
int tileNum=10,tileNumSq; // number of tiles
int tileImgCnt, tileX, tileY, tilePad;
boolean firstFrame=false, secondFrame=false;
String tileFilename,tileFileextension=".png";
PImage tileImg;
float perc,percMilestone;
// The constructor takes a PApplet reference to your sketch.
public TileSaver(PApplet _p) {
p=_p;
}
// If init() is called without specifying number of tiles, getMaxTiles()
// will be called to estimate number of tiles according to free memory.
public void init(String _filename) {
init(_filename,getMaxTiles(p.width));
}
// Initialize using a filename to output to and number of tiles to use.
public void init(String _filename,int _num) {
tileFilename=_filename;
tileNum=_num;
tileNumSq=(tileNum*tileNum);
width=p.width;
height=p.height;
cameraZ=(height/2.0f)/p.tan(p.PI*FOV/360.0f);
p.println("TileSaver: "+tileNum+" tilesnResolution: "+
(p.width*tileNum)+"x"+(p.height*tileNum));
// remove extension from filename
if(!new java.io.File(tileFilename).isAbsolute())
tileFilename=p.sketchPath(tileFilename);
tileFilename=noExt(tileFilename);
p.createPath(tileFilename);
// save preview
if(doSavePreview) p.g.save(tileFilename+"_preview.png");
// set up off-screen buffer for saving tiled images
tileImg=new PImage(p.width*tileNum, p.height*tileNum);
// start tiling
done=false;
isTiling=false;
perc=0;
percMilestone=0;
tileInc();
}
// set filetype, default is TGA. pass a valid image extension as parameter.
public void setSaveType(String extension) {
tileFileextension=extension;
if(tileFileextension.indexOf(".")==-1) tileFileextension="."+tileFileextension;
}
// pre() handles initialization of each frame.
// It should be called in draw() before any drawing occurs.
public void pre() {
if(!isTiling) return;
if(firstFrame) firstFrame=false;
else if(secondFrame) {
secondFrame=false;
}
setupCamera();
}
// post() handles tile update and image saving.
// It should be called at the very end of draw(), after any drawing.
public void post() {
// If first or second frame, don't update or save.
if(firstFrame||secondFrame|| (!isTiling)) return;
// Find image ID from reverse row order
int imgid=tileImgCnt%tileNum+(tileNum-tileImgCnt/tileNum-1)*tileNum;
int idx=(imgid+0)%tileNum;
int idy=(imgid/tileNum);
// Get current image from sketch and draw it into buffer
p.loadPixels();
tileImg.set(idx*p.width, idy*p.height, p.g);
// Increment tile index
tileImgCnt++;
perc=100*((float)tileImgCnt/(float)tileNumSq);
if(perc-percMilestone>5 || perc>99) {
p.println(p.nf(perc,3,2)+"% completed. "+tileImgCnt+"/"+tileNumSq+" images saved.");
percMilestone=perc;
}
if(tileImgCnt==tileNumSq) tileFinish();
else tileInc();
}
public boolean checkStatus() {
return isTiling;
}
// tileFinish() handles saving of the tiled image
public void tileFinish() {
isTiling=false;
restoreCamera();
// save large image to TGA
tileFilename+="_"+(p.width*tileNum)+"x"+
(p.height*tileNum)+tileFileextension;
p.println("Save: "+
tileFilename.substring(
tileFilename.lastIndexOf(java.io.File.separator)+1));
tileImg.save(tileFilename);
p.println("Done tiling.n");
// clear buffer for garbage collection
tileImg=null;
done=true;
}
// Increment tile coordinates
public void tileInc() {
if(!isTiling) {
isTiling=true;
firstFrame=true;
secondFrame=true;
tileImgCnt=0;
}
else {
if(tileX==tileNum-1) {
tileX=0;
tileY=(tileY+1)%tileNum;
}
else
tileX++;
}
}
// set up camera correctly for the current tile
public void setupCamera() {
p.camera(width/2.0f, height/2.0f, cameraZ,
width/2.0f, height/2.0f, 0, 0, 1, 0);
if(isTiling) {
float mod=1f/10f;
p.frustum(width*((float)tileX/(float)tileNum-.5f)*mod,
width*((tileX+1)/(float)tileNum-.5f)*mod,
height*((float)tileY/(float)tileNum-.5f)*mod,
height*((tileY+1)/(float)tileNum-.5f)*mod,
cameraZ*mod, 10000);
}
}
// restore camera once tiling is done
public void restoreCamera() {
float mod=1f/10f;
p.camera(width/2.0f, height/2.0f, cameraZ,
width/2.0f, height/2.0f, 0, 0, 1, 0);
p.frustum(-(width/2)*mod, (width/2)*mod,
-(height/2)*mod, (height/2)*mod,
cameraZ*mod, 10000);
}
// checks free memory and gives a suggestion for maximum tile
// resolution. It should work well in most cases, I've been able
// to generate 20k x 20k pixel images with 1.5 GB RAM allocated.
public int getMaxTiles(int width) {
// get an instance of java.lang.Runtime, force garbage collection
java.lang.Runtime runtime=java.lang.Runtime.getRuntime();
runtime.gc();
// calculate free memory for ARGB (4 byte) data, giving some slack
// to out of memory crashes.
int num=(int)(Math.sqrt(
(float)(runtime.freeMemory()/4)*0.925f))/width;
p.println(((float)runtime.freeMemory()/(1024*1024))+"/"+
((float)runtime.totalMemory()/(1024*1024)));
// warn if low memory
if(num==1) {
p.println("Memory is low. Consider increasing memory settings.");
num=2;
}
return num;
}
// strip extension from filename
String noExt(String name) {
int last=name.lastIndexOf(".");
if(last>0)
return name.substring(0, last);
return name;
}
}
Update