squares, turning
quick p5.js fork 'n port of beesandbombs squares, turning.
This version will render out an animated gif on mouseclick.
license: mit | |
height: 480 |
squares, turning
quick p5.js fork 'n port of beesandbombs squares, turning.
This version will render out an animated gif on mouseclick.
function encode64(input) { | |
var output = "", i = 0, l = input.length, | |
key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", | |
chr1, chr2, chr3, enc1, enc2, enc3, enc4; | |
while (i < l) { | |
chr1 = input.charCodeAt(i++); | |
chr2 = input.charCodeAt(i++); | |
chr3 = input.charCodeAt(i++); | |
enc1 = chr1 >> 2; | |
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); | |
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); | |
enc4 = chr3 & 63; | |
if (isNaN(chr2)) enc3 = enc4 = 64; | |
else if (isNaN(chr3)) enc4 = 64; | |
output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4); | |
} | |
return output; | |
} |
int[][] result; | |
float t, c; | |
float ease(float p) { | |
return 3*p*p - 2*p*p*p; | |
} | |
float ease(float p, float g) { | |
if (p < 0.5) | |
return 0.5 * pow(2*p, g); | |
else | |
return 1 - 0.5 * pow(2*(1 - p), g); | |
} | |
float mn = .5*sqrt(3), ia = atan(sqrt(.5)); | |
void push() { | |
pushMatrix(); | |
pushStyle(); | |
} | |
void pop() { | |
popStyle(); | |
popMatrix(); | |
} | |
float c01(float g) { | |
return constrain(g, 0, 1); | |
} | |
void draw() { | |
if (!recording) { | |
t = mouseX*1.0/width; | |
c = mouseY*1.0/height; | |
if (mousePressed) | |
println(c); | |
draw_(); | |
} else { | |
for (int i=0; i<width*height; i++) | |
for (int a=0; a<3; a++) | |
result[i][a] = 0; | |
c = 0; | |
for (int sa=0; sa<samplesPerFrame; sa++) { | |
t = map(frameCount-1 + sa*shutterAngle/samplesPerFrame, 0, numFrames, 0, 1); | |
draw_(); | |
loadPixels(); | |
for (int i=0; i<pixels.length; i++) { | |
result[i][0] += pixels[i] >> 16 & 0xff; | |
result[i][1] += pixels[i] >> 8 & 0xff; | |
result[i][2] += pixels[i] & 0xff; | |
} | |
} | |
loadPixels(); | |
for (int i=0; i<pixels.length; i++) | |
pixels[i] = 0xff << 24 | | |
int(result[i][0]*1.0/samplesPerFrame) << 16 | | |
int(result[i][1]*1.0/samplesPerFrame) << 8 | | |
int(result[i][2]*1.0/samplesPerFrame); | |
updatePixels(); | |
saveFrame("f###.gif"); | |
if (frameCount==numFrames) | |
exit(); | |
} | |
} | |
////////////////////////////////////////////////////////////////////////////// | |
int samplesPerFrame = 4; | |
int numFrames = 360; | |
float shutterAngle = .6; | |
boolean recording = false; | |
void setup() { | |
size(720, 720, P3D); | |
pixelDensity(recording ? 1 : 2); | |
smooth(8); | |
result = new int[width*height][3]; | |
rectMode(CENTER); | |
fill(32); | |
noStroke(); | |
} | |
float x, y, z, tt; | |
int N = 12; | |
float w = 44, h= w*3, sp = h; | |
void cros() { | |
rect(0, 0, w, h); | |
rect(0, 0, h, w); | |
} | |
void draw_() { | |
push(); | |
translate(width/2, height/2); | |
if (3*t < 1) { | |
t = 3*t; | |
background(234); | |
fill(34); | |
for (int i=-N; i<=N; i++) { | |
for (int j=-N; j<=N; j++) { | |
tt = ease(t, 3); | |
push(); | |
translate(i*h - j*w*tt, j*h + i*w*tt); | |
rotate(HALF_PI*tt); | |
cros(); | |
pop(); | |
} | |
} | |
} | |
else if (3*t < 2) { | |
t = 3*t - 1; | |
background(34); | |
fill(234); | |
push(); | |
scale(-1, 1); | |
for (int i=-N; i<=N; i++) { | |
for (int j=-N; j<=N; j++) { | |
tt = 1-ease(t, 3); | |
push(); | |
translate((i+.5)*h + (j+.5)*w*tt, (j+.5)*h - (i+.5)*w*tt); | |
rotate(-HALF_PI*tt); | |
cros(); | |
pop(); | |
} | |
} | |
pop(); | |
} | |
else { | |
t = 3*t - 2; | |
background(t>.5?34:234); | |
fill(t>.5?234:34); | |
for (int i=-N; i<=N; i++) { | |
for (int j=-N; j<=N; j++) { | |
tt = ease(t, 3); | |
push(); | |
translate((i+.5*(t>.5?1:0))*h, (j+.5*(t>.5?1:0))*h); | |
rotate(HALF_PI*tt); | |
scale(map(cos(TWO_PI*tt), 1, -1, 1, sqrt(2)*3/4)); | |
rect(0, 0, 2*w, 2*w); | |
pop(); | |
} | |
} | |
} | |
pop(); | |
} |
/** | |
* This class lets you encode animated GIF files | |
* Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm | |
* @author Kevin Weiner (original Java version - kweiner@fmsware.com) | |
* @author Thibault Imbert (AS3 version - bytearray.org) | |
* @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) | |
* @version 0.1 AS3 implementation | |
*/ | |
GIFEncoder = function() { | |
for (var i = 0, chr = {}; i < 256; i++) | |
chr[i] = String.fromCharCode(i); | |
function ByteArray() { | |
this.bin = []; | |
} | |
ByteArray.prototype.getData = function() { | |
for (var v = '', l = this.bin.length, i = 0; i < l; i++) | |
v += chr[this.bin[i]]; | |
return v; | |
}; | |
ByteArray.prototype.writeByte = function(val) { | |
this.bin.push(val); | |
}; | |
ByteArray.prototype.writeUTFBytes = function(string) { | |
for (var l = string.length, i = 0; i < l; i++) | |
this.writeByte(string.charCodeAt(i)); | |
}; | |
ByteArray.prototype.writeBytes = function(array, offset, length) { | |
for (var l = length || array.length, i = offset || 0; i < l; i++) | |
this.writeByte(array[i]); | |
}; | |
var exports = {}; | |
var width; // image size | |
var height; | |
var transparent = null; // transparent color if given | |
var transIndex; // transparent index in color table | |
var repeat = -1; // no repeat | |
var delay = 0; // frame delay (hundredths) | |
var started = false; // ready to output frames | |
var out; | |
var image; // current frame | |
var pixels; // BGR byte array from frame | |
var indexedPixels; // converted frame indexed to palette | |
var colorDepth; // number of bit planes | |
var colorTab; // RGB palette | |
var usedEntry = []; // active palette entries | |
var palSize = 7; // color table size (bits-1) | |
var dispose = -1; // disposal code (-1 = use default) | |
var closeStream = false; // close stream when finished | |
var firstFrame = true; | |
var sizeSet = false; // if false, get size from first frame | |
var sample = 10; // default sample interval for quantizer | |
var comment = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif | |
/** | |
* Sets the delay time between each frame, or changes it for subsequent frames | |
* (applies to last frame added) | |
* int delay time in milliseconds | |
* @param ms | |
*/ | |
var setDelay = exports.setDelay = function setDelay(ms) { | |
delay = Math.round(ms / 10); | |
}; | |
/** | |
* Sets the GIF frame disposal code for the last added frame and any | |
* | |
* subsequent frames. Default is 0 if no transparent color has been set, | |
* otherwise 2. | |
* @param code | |
* int disposal code. | |
*/ | |
var setDispose = exports.setDispose = function setDispose(code) { | |
if (code >= 0) dispose = code; | |
}; | |
/** | |
* Sets the number of times the set of GIF frames should be played. Default is | |
* 1; 0 means play indefinitely. Must be invoked before the first image is | |
* added. | |
* | |
* @param iter | |
* int number of iterations. | |
* @return | |
*/ | |
var setRepeat = exports.setRepeat = function setRepeat(iter) { | |
if (iter >= 0) repeat = iter; | |
}; | |
/** | |
* Sets the transparent color for the last added frame and any subsequent | |
* frames. Since all colors are subject to modification in the quantization | |
* process, the color in the final palette for each frame closest to the given | |
* color becomes the transparent color for that frame. May be set to null to | |
* indicate no transparent color. | |
* @param | |
* Color to be treated as transparent on display. | |
*/ | |
var setTransparent = exports.setTransparent = function setTransparent(c) { | |
transparent = c; | |
}; | |
/** | |
* Sets the comment for the block comment | |
* @param | |
* string to be insterted as comment | |
*/ | |
var setComment = exports.setComment = function setComment(c) { | |
comment = c; | |
}; | |
/** | |
* The addFrame method takes an incoming BitmapData object to create each frames | |
* @param | |
* BitmapData object to be treated as a GIF's frame | |
*/ | |
var addFrame = exports.addFrame = function addFrame(im, is_imageData) { | |
if ((im === null) || !started || out === null) { | |
throw new Error("Please call start method before calling addFrame"); | |
} | |
var ok = true; | |
try { | |
if (!is_imageData) { | |
image = im.getImageData(0, 0, im.canvas.width, im.canvas.height).data; | |
if (!sizeSet) setSize(im.canvas.width, im.canvas.height); | |
} else { | |
if(im instanceof ImageData) { | |
image = im.data; | |
if(!sizeset || width!=im.width || height!=im.height) { | |
setSize(im.width,im.height); | |
} else { | |
} | |
} else if(im instanceof Uint8ClampedArray) { | |
if(im.length==(width*height*4)) { | |
image=im; | |
} else { | |
console.log("Please set the correct size: ImageData length mismatch"); | |
ok=false; | |
} | |
} else { | |
console.log("Please provide correct input"); | |
ok=false; | |
} | |
} | |
getImagePixels(); // convert to correct format if necessary | |
analyzePixels(); // build color table & map pixels | |
if (firstFrame) { | |
writeLSD(); // logical screen descriptior | |
writePalette(); // global color table | |
if (repeat >= 0) { | |
// use NS app extension to indicate reps | |
writeNetscapeExt(); | |
} | |
} | |
writeGraphicCtrlExt(); // write graphic control extension | |
if (comment !== '') { | |
writeCommentExt(); // write comment extension | |
} | |
writeImageDesc(); // image descriptor | |
if (!firstFrame) writePalette(); // local color table | |
writePixels(); // encode and write pixel data | |
firstFrame = false; | |
} catch (e) { | |
ok = false; | |
} | |
return ok; | |
}; | |
/** | |
* @description: Downloads the encoded gif with the given name | |
* No need of any conversion from the stream data (out) to base64 | |
* Solves the issue of large file sizes when there are more frames | |
* and does not involve in creation of any temporary data in the process | |
* so no wastage of memory, and speeds up the process of downloading | |
* to just calling this function. | |
* @parameter {String} filename filename used for downloading the gif | |
*/ | |
var download = exports.download = function download(filename) { | |
if(out===null || closeStream==false) { | |
console.log("Please call start method and add frames and call finish method before calling download"); | |
} else { | |
filename= filename !== undefined ? ( filename.endsWith(".gif")? filename: filename+".gif" ): "download.gif"; | |
var templink = document.createElement("a"); | |
templink.download=filename; | |
templink.href= URL.createObjectURL(new Blob([new Uint8Array(out.bin)], {type : "image/gif" } )); | |
templink.click(); | |
} | |
} | |
/** | |
* Adds final trailer to the GIF stream, if you don't call the finish method | |
* the GIF stream will not be valid. | |
*/ | |
var finish = exports.finish = function finish() { | |
if (!started) return false; | |
var ok = true; | |
started = false; | |
try { | |
out.writeByte(0x3b); // gif trailer | |
closeStream=true; | |
} catch (e) { | |
ok = false; | |
} | |
return ok; | |
}; | |
/** | |
* Resets some members so that a new stream can be started. | |
* This method is actually called by the start method | |
*/ | |
var reset = function reset() { | |
// reset for subsequent use | |
transIndex = 0; | |
image = null; | |
pixels = null; | |
indexedPixels = null; | |
colorTab = null; | |
closeStream = false; | |
firstFrame = true; | |
}; | |
/** | |
* * Sets frame rate in frames per second. Equivalent to | |
* <code>setDelay(1000/fps)</code>. | |
* @param fps | |
* float frame rate (frames per second) | |
*/ | |
var setFrameRate = exports.setFrameRate = function setFrameRate(fps) { | |
if (fps != 0xf) delay = Math.round(100 / fps); | |
}; | |
/** | |
* Sets quality of color quantization (conversion of images to the maximum 256 | |
* colors allowed by the GIF specification). Lower values (minimum = 1) | |
* produce better colors, but slow processing significantly. 10 is the | |
* default, and produces good color mapping at reasonable speeds. Values | |
* greater than 20 do not yield significant improvements in speed. | |
* @param quality | |
* int greater than 0. | |
* @return | |
*/ | |
var setQuality = exports.setQuality = function setQuality(quality) { | |
if (quality < 1) quality = 1; | |
sample = quality; | |
}; | |
/** | |
* Sets the GIF frame size. The default size is the size of the first frame | |
* added if this method is not invoked. | |
* @param w | |
* int frame width. | |
* @param h | |
* int frame width. | |
*/ | |
var setSize = exports.setSize = function setSize(w, h) { | |
if (started && !firstFrame) return; | |
width = w; | |
height = h; | |
if (width < 1) width = 320; | |
if (height < 1) height = 240; | |
sizeSet = true; | |
}; | |
/** | |
* Initiates GIF file creation on the given stream. | |
* @param os | |
* OutputStream on which GIF images are written. | |
* @return false if initial write failed. | |
*/ | |
var start = exports.start = function start() { | |
reset(); | |
var ok = true; | |
closeStream = false; | |
out = new ByteArray(); | |
try { | |
out.writeUTFBytes("GIF89a"); // header | |
} catch (e) { | |
ok = false; | |
} | |
return started = ok; | |
}; | |
var cont = exports.cont = function cont() { | |
reset(); | |
var ok = true; | |
closeStream = false; | |
out = new ByteArray(); | |
return started = ok; | |
}; | |
/** | |
* Analyzes image colors and creates color map. | |
*/ | |
var analyzePixels = function analyzePixels() { | |
var len = pixels.length; | |
var nPix = len / 3; | |
indexedPixels = []; | |
var nq = new NeuQuant(pixels, len, sample); | |
// initialize quantizer | |
colorTab = nq.process(); // create reduced palette | |
// map image pixels to new palette | |
var k = 0; | |
for (var j = 0; j < nPix; j++) { | |
var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff); | |
usedEntry[index] = true; | |
indexedPixels[j] = index; | |
} | |
pixels = null; | |
colorDepth = 8; | |
palSize = 7; | |
// get closest match to transparent color if specified | |
if (transparent !== null) { | |
transIndex = findClosest(transparent); | |
} | |
}; | |
/** | |
* Returns index of palette color closest to c | |
*/ | |
var findClosest = function findClosest(c) { | |
if (colorTab === null) return -1; | |
var r = (c & 0xFF0000) >> 16; | |
var g = (c & 0x00FF00) >> 8; | |
var b = (c & 0x0000FF); | |
var minpos = 0; | |
var dmin = 256 * 256 * 256; | |
var len = colorTab.length; | |
for (var i = 0; i < len;) { | |
var dr = r - (colorTab[i++] & 0xff); | |
var dg = g - (colorTab[i++] & 0xff); | |
var db = b - (colorTab[i] & 0xff); | |
var d = dr * dr + dg * dg + db * db; | |
var index = i / 3; | |
if (usedEntry[index] && (d < dmin)) { | |
dmin = d; | |
minpos = index; | |
} | |
i++; | |
} | |
return minpos; | |
}; | |
/** | |
* Extracts image pixels into byte array "pixels | |
*/ | |
var getImagePixels = function getImagePixels() { | |
var w = width; | |
var h = height; | |
pixels = []; | |
var data = image; | |
var count = 0; | |
for (var i = 0; i < h; i++) { | |
for (var j = 0; j < w; j++) { | |
var b = (i * w * 4) + j * 4; | |
pixels[count++] = data[b]; | |
pixels[count++] = data[b + 1]; | |
pixels[count++] = data[b + 2]; | |
} | |
} | |
}; | |
/** | |
* Writes Graphic Control Extension | |
*/ | |
var writeGraphicCtrlExt = function writeGraphicCtrlExt() { | |
out.writeByte(0x21); // extension introducer | |
out.writeByte(0xf9); // GCE label | |
out.writeByte(4); // data block size | |
var transp; | |
var disp; | |
if (transparent === null) { | |
transp = 0; | |
disp = 0; // dispose = no action | |
} else { | |
transp = 1; | |
disp = 2; // force clear if using transparent color | |
} | |
if (dispose >= 0) { | |
disp = dispose & 7; // user override | |
} | |
disp <<= 2; | |
// packed fields | |
out.writeByte(0 | // 1:3 reserved | |
disp | // 4:6 disposal | |
0 | // 7 user input - 0 = none | |
transp); // 8 transparency flag | |
WriteShort(delay); // delay x 1/100 sec | |
out.writeByte(transIndex); // transparent color index | |
out.writeByte(0); // block terminator | |
}; | |
/** | |
* Writes Comment Extention | |
*/ | |
var writeCommentExt = function writeCommentExt() { | |
out.writeByte(0x21); // extension introducer | |
out.writeByte(0xfe); // comment label | |
out.writeByte(comment.length); // Block Size (s) | |
out.writeUTFBytes(comment); | |
out.writeByte(0); // block terminator | |
}; | |
/** | |
* Writes Image Descriptor | |
*/ | |
var writeImageDesc = function writeImageDesc() { | |
out.writeByte(0x2c); // image separator | |
WriteShort(0); // image position x,y = 0,0 | |
WriteShort(0); | |
WriteShort(width); // image size | |
WriteShort(height); | |
// packed fields | |
if (firstFrame) { | |
// no LCT - GCT is used for first (or only) frame | |
out.writeByte(0); | |
} else { | |
// specify normal LCT | |
out.writeByte(0x80 | // 1 local color table 1=yes | |
0 | // 2 interlace - 0=no | |
0 | // 3 sorted - 0=no | |
0 | // 4-5 reserved | |
palSize); // 6-8 size of color table | |
} | |
}; | |
/** | |
* Writes Logical Screen Descriptor | |
*/ | |
var writeLSD = function writeLSD() { | |
// logical screen size | |
WriteShort(width); | |
WriteShort(height); | |
// packed fields | |
out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used) | |
0x70 | // 2-4 : color resolution = 7 | |
0x00 | // 5 : gct sort flag = 0 | |
palSize)); // 6-8 : gct size | |
out.writeByte(0); // background color index | |
out.writeByte(0); // pixel aspect ratio - assume 1:1 | |
}; | |
/** | |
* Writes Netscape application extension to define repeat count. | |
*/ | |
var writeNetscapeExt = function writeNetscapeExt() { | |
out.writeByte(0x21); // extension introducer | |
out.writeByte(0xff); // app extension label | |
out.writeByte(11); // block size | |
out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code | |
out.writeByte(3); // sub-block size | |
out.writeByte(1); // loop sub-block id | |
WriteShort(repeat); // loop count (extra iterations, 0=repeat forever) | |
out.writeByte(0); // block terminator | |
}; | |
/** | |
* Writes color table | |
*/ | |
var writePalette = function writePalette() { | |
out.writeBytes(colorTab); | |
var n = (3 * 256) - colorTab.length; | |
for (var i = 0; i < n; i++) out.writeByte(0); | |
}; | |
var WriteShort = function WriteShort(pValue) { | |
out.writeByte(pValue & 0xFF); | |
out.writeByte((pValue >> 8) & 0xFF); | |
}; | |
/** | |
* Encodes and writes pixel data | |
*/ | |
var writePixels = function writePixels() { | |
var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth); | |
myencoder.encode(out); | |
}; | |
/** | |
* Retrieves the GIF stream | |
*/ | |
var stream = exports.stream = function stream() { | |
return out; | |
}; | |
var setProperties = exports.setProperties = function setProperties(has_start, is_first) { | |
started = has_start; | |
firstFrame = is_first; | |
}; | |
return exports; | |
}; |
<head> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/p5.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/addons/p5.dom.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/seedrandom.min.js"></script> | |
<script src="https://d3js.org/d3-random.v1.min.js"></script> | |
<script language="javascript" type="text/javascript" src="z_purview_helper.js"></script> | |
<script language="javascript" type="text/javascript" src="z_focused_random.js"></script> | |
<script type="text/javascript" src="LZWEncoder.js"></script> | |
<script type="text/javascript" src="NeuQuant.js"></script> | |
<script type="text/javascript" src="GIFEncoder.js"></script> | |
<script type="text/javascript" src="b64.js"></script> | |
<script type="text/javascript" src="z_recorder.js"></script> | |
<script language="javascript" type="text/javascript" src="squaresturning.js"></script> | |
<style> body {padding: 0; margin: 0;} </style> | |
</head> | |
<body style="background-color:white"> | |
</body> |
/** | |
* This class handles LZW encoding | |
* Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. | |
* @author Kevin Weiner (original Java version - kweiner@fmsware.com) | |
* @author Thibault Imbert (AS3 version - bytearray.org) | |
* @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) | |
* @version 0.1 AS3 implementation | |
*/ | |
LZWEncoder = function() { | |
var exports = {}; | |
var EOF = -1; | |
var imgW; | |
var imgH; | |
var pixAry; | |
var initCodeSize; | |
var remaining; | |
var curPixel; | |
// GIFCOMPR.C - GIF Image compression routines | |
// Lempel-Ziv compression based on 'compress'. GIF modifications by | |
// David Rowley (mgardi@watdcsu.waterloo.edu) | |
// General DEFINEs | |
var BITS = 12; | |
var HSIZE = 5003; // 80% occupancy | |
// GIF Image compression - modified 'compress' | |
// Based on: compress.c - File compression ala IEEE Computer, June 1984. | |
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) | |
// Jim McKie (decvax!mcvax!jim) | |
// Steve Davies (decvax!vax135!petsd!peora!srd) | |
// Ken Turkowski (decvax!decwrl!turtlevax!ken) | |
// James A. Woods (decvax!ihnp4!ames!jaw) | |
// Joe Orost (decvax!vax135!petsd!joe) | |
var n_bits; // number of bits/code | |
var maxbits = BITS; // user settable max # bits/code | |
var maxcode; // maximum code, given n_bits | |
var maxmaxcode = 1 << BITS; // should NEVER generate this code | |
var htab = []; | |
var codetab = []; | |
var hsize = HSIZE; // for dynamic table sizing | |
var free_ent = 0; // first unused entry | |
// block compression parameters -- after all codes are used up, | |
// and compression rate changes, start over. | |
var clear_flg = false; | |
// Algorithm: use open addressing double hashing (no chaining) on the | |
// prefix code / next character combination. We do a variant of Knuth's | |
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime | |
// secondary probe. Here, the modular division first probe is gives way | |
// to a faster exclusive-or manipulation. Also do block compression with | |
// an adaptive reset, whereby the code table is cleared when the compression | |
// ratio decreases, but after the table fills. The variable-length output | |
// codes are re-sized at this point, and a special CLEAR code is generated | |
// for the decompressor. Late addition: construct the table according to | |
// file size for noticeable speed improvement on small files. Please direct | |
// questions about this implementation to ames!jaw. | |
var g_init_bits; | |
var ClearCode; | |
var EOFCode; | |
// output | |
// Output the given code. | |
// Inputs: | |
// code: A n_bits-bit integer. If == -1, then EOF. This assumes | |
// that n_bits =< wordsize - 1. | |
// Outputs: | |
// Outputs code to the file. | |
// Assumptions: | |
// Chars are 8 bits long. | |
// Algorithm: | |
// Maintain a BITS character long buffer (so that 8 codes will | |
// fit in it exactly). Use the VAX insv instruction to insert each | |
// code in turn. When the buffer fills up empty it and start over. | |
var cur_accum = 0; | |
var cur_bits = 0; | |
var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; | |
// Number of characters so far in this 'packet' | |
var a_count; | |
// Define the storage for the packet accumulator | |
var accum = []; | |
var LZWEncoder = exports.LZWEncoder = function LZWEncoder(width, height, pixels, color_depth) { | |
imgW = width; | |
imgH = height; | |
pixAry = pixels; | |
initCodeSize = Math.max(2, color_depth); | |
}; | |
// Add a character to the end of the current packet, and if it is 254 | |
// characters, flush the packet to disk. | |
var char_out = function char_out(c, outs) { | |
accum[a_count++] = c; | |
if (a_count >= 254) flush_char(outs); | |
}; | |
// Clear out the hash table | |
// table clear for block compress | |
var cl_block = function cl_block(outs) { | |
cl_hash(hsize); | |
free_ent = ClearCode + 2; | |
clear_flg = true; | |
output(ClearCode, outs); | |
}; | |
// reset code table | |
var cl_hash = function cl_hash(hsize) { | |
for (var i = 0; i < hsize; ++i) htab[i] = -1; | |
}; | |
var compress = exports.compress = function compress(init_bits, outs) { | |
var fcode; | |
var i; /* = 0 */ | |
var c; | |
var ent; | |
var disp; | |
var hsize_reg; | |
var hshift; | |
// Set up the globals: g_init_bits - initial number of bits | |
g_init_bits = init_bits; | |
// Set up the necessary values | |
clear_flg = false; | |
n_bits = g_init_bits; | |
maxcode = MAXCODE(n_bits); | |
ClearCode = 1 << (init_bits - 1); | |
EOFCode = ClearCode + 1; | |
free_ent = ClearCode + 2; | |
a_count = 0; // clear packet | |
ent = nextPixel(); | |
hshift = 0; | |
for (fcode = hsize; fcode < 65536; fcode *= 2) | |
++hshift; | |
hshift = 8 - hshift; // set hash code range bound | |
hsize_reg = hsize; | |
cl_hash(hsize_reg); // clear hash table | |
output(ClearCode, outs); | |
outer_loop: while ((c = nextPixel()) != EOF) { | |
fcode = (c << maxbits) + ent; | |
i = (c << hshift) ^ ent; // xor hashing | |
if (htab[i] == fcode) { | |
ent = codetab[i]; | |
continue; | |
} | |
else if (htab[i] >= 0) { // non-empty slot | |
disp = hsize_reg - i; // secondary hash (after G. Knott) | |
if (i === 0) disp = 1; | |
do { | |
if ((i -= disp) < 0) | |
i += hsize_reg; | |
if (htab[i] == fcode) { | |
ent = codetab[i]; | |
continue outer_loop; | |
} | |
} while (htab[i] >= 0); | |
} | |
output(ent, outs); | |
ent = c; | |
if (free_ent < maxmaxcode) { | |
codetab[i] = free_ent++; // code -> hashtable | |
htab[i] = fcode; | |
} | |
else cl_block(outs); | |
} | |
// Put out the final code. | |
output(ent, outs); | |
output(EOFCode, outs); | |
}; | |
// ---------------------------------------------------------------------------- | |
var encode = exports.encode = function encode(os) { | |
os.writeByte(initCodeSize); // write "initial code size" byte | |
remaining = imgW * imgH; // reset navigation variables | |
curPixel = 0; | |
compress(initCodeSize + 1, os); // compress and write the pixel data | |
os.writeByte(0); // write block terminator | |
}; | |
// Flush the packet to disk, and reset the accumulator | |
var flush_char = function flush_char(outs) { | |
if (a_count > 0) { | |
outs.writeByte(a_count); | |
outs.writeBytes(accum, 0, a_count); | |
a_count = 0; | |
} | |
}; | |
var MAXCODE = function MAXCODE(n_bits) { | |
return (1 << n_bits) - 1; | |
}; | |
// ---------------------------------------------------------------------------- | |
// Return the next pixel from the image | |
// ---------------------------------------------------------------------------- | |
var nextPixel = function nextPixel() { | |
if (remaining === 0) return EOF; | |
--remaining; | |
var pix = pixAry[curPixel++]; | |
return pix & 0xff; | |
}; | |
var output = function output(code, outs) { | |
cur_accum &= masks[cur_bits]; | |
if (cur_bits > 0) cur_accum |= (code << cur_bits); | |
else cur_accum = code; | |
cur_bits += n_bits; | |
while (cur_bits >= 8) { | |
char_out((cur_accum & 0xff), outs); | |
cur_accum >>= 8; | |
cur_bits -= 8; | |
} | |
// If the next entry is going to be too big for the code size, | |
// then increase it, if possible. | |
if (free_ent > maxcode || clear_flg) { | |
if (clear_flg) { | |
maxcode = MAXCODE(n_bits = g_init_bits); | |
clear_flg = false; | |
} else { | |
++n_bits; | |
if (n_bits == maxbits) maxcode = maxmaxcode; | |
else maxcode = MAXCODE(n_bits); | |
} | |
} | |
if (code == EOFCode) { | |
// At EOF, write the rest of the buffer. | |
while (cur_bits > 0) { | |
char_out((cur_accum & 0xff), outs); | |
cur_accum >>= 8; | |
cur_bits -= 8; | |
} | |
flush_char(outs); | |
} | |
}; | |
LZWEncoder.apply(this, arguments); | |
return exports; | |
}; |
/* | |
* NeuQuant Neural-Net Quantization Algorithm | |
* ------------------------------------------ | |
* | |
* Copyright (c) 1994 Anthony Dekker | |
* | |
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See | |
* "Kohonen neural networks for optimal colour quantization" in "Network: | |
* Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of | |
* the algorithm. | |
* | |
* Any party obtaining a copy of these files from the author, directly or | |
* indirectly, is granted, free of charge, a full and unrestricted irrevocable, | |
* world-wide, paid up, royalty-free, nonexclusive right and license to deal in | |
* this software and documentation files (the "Software"), including without | |
* limitation the rights to use, copy, modify, merge, publish, distribute, | |
* sublicense, and/or sell copies of the Software, and to permit persons who | |
* receive copies from any such party to do so, with the only requirement being | |
* that this copyright notice remain intact. | |
*/ | |
/* | |
* This class handles Neural-Net quantization algorithm | |
* @author Kevin Weiner (original Java version - kweiner@fmsware.com) | |
* @author Thibault Imbert (AS3 version - bytearray.org) | |
* @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) | |
* @version 0.1 AS3 implementation | |
*/ | |
NeuQuant = function() { | |
var exports = {}; | |
var netsize = 256; /* number of colours used */ | |
/* four primes near 500 - assume no image has a length so large */ | |
/* that it is divisible by all four primes */ | |
var prime1 = 499; | |
var prime2 = 491; | |
var prime3 = 487; | |
var prime4 = 503; | |
var minpicturebytes = (3 * prime4); /* minimum size for input image */ | |
/* | |
* Program Skeleton ---------------- [select samplefac in range 1..30] [read | |
* image from input file] pic = (unsigned char*) malloc(3*width*height); | |
* initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output | |
* image header, using writecolourmap(f)] inxbuild(); write output image using | |
* inxsearch(b,g,r) | |
*/ | |
/* | |
* Network Definitions ------------------- | |
*/ | |
var maxnetpos = (netsize - 1); | |
var netbiasshift = 4; /* bias for colour values */ | |
var ncycles = 100; /* no. of learning cycles */ | |
/* defs for freq and bias */ | |
var intbiasshift = 16; /* bias for fractions */ | |
var intbias = (1 << intbiasshift); | |
var gammashift = 10; /* gamma = 1024 */ | |
var gamma = (1 << gammashift); | |
var betashift = 10; | |
var beta = (intbias >> betashift); /* beta = 1/1024 */ | |
var betagamma = (intbias << (gammashift - betashift)); | |
/* defs for decreasing radius factor */ | |
var initrad = (netsize >> 3); /* for 256 cols, radius starts */ | |
var radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ | |
var radiusbias = (1 << radiusbiasshift); | |
var initradius = (initrad * radiusbias); /* and decreases by a */ | |
var radiusdec = 30; /* factor of 1/30 each cycle */ | |
/* defs for decreasing alpha factor */ | |
var alphabiasshift = 10; /* alpha starts at 1.0 */ | |
var initalpha = (1 << alphabiasshift); | |
var alphadec; /* biased by 10 bits */ | |
/* radbias and alpharadbias used for radpower calculation */ | |
var radbiasshift = 8; | |
var radbias = (1 << radbiasshift); | |
var alpharadbshift = (alphabiasshift + radbiasshift); | |
var alpharadbias = (1 << alpharadbshift); | |
/* | |
* Types and Global Variables -------------------------- | |
*/ | |
var thepicture; /* the input image itself */ | |
var lengthcount; /* lengthcount = H*W*3 */ | |
var samplefac; /* sampling factor 1..30 */ | |
// typedef int pixel[4]; /* BGRc */ | |
var network; /* the network itself - [netsize][4] */ | |
var netindex = []; | |
/* for network lookup - really 256 */ | |
var bias = []; | |
/* bias and freq arrays for learning */ | |
var freq = []; | |
var radpower = []; | |
var NeuQuant = exports.NeuQuant = function NeuQuant(thepic, len, sample) { | |
var i; | |
var p; | |
thepicture = thepic; | |
lengthcount = len; | |
samplefac = sample; | |
network = new Array(netsize); | |
for (i = 0; i < netsize; i++) { | |
network[i] = new Array(4); | |
p = network[i]; | |
p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; | |
freq[i] = intbias / netsize; /* 1/netsize */ | |
bias[i] = 0; | |
} | |
}; | |
var colorMap = function colorMap() { | |
var map = []; | |
var index = new Array(netsize); | |
for (var i = 0; i < netsize; i++) | |
index[network[i][3]] = i; | |
var k = 0; | |
for (var l = 0; l < netsize; l++) { | |
var j = index[l]; | |
map[k++] = (network[j][0]); | |
map[k++] = (network[j][1]); | |
map[k++] = (network[j][2]); | |
} | |
return map; | |
}; | |
/* | |
* Insertion sort of network and building of netindex[0..255] (to do after | |
* unbias) | |
* ------------------------------------------------------------------------------- | |
*/ | |
var inxbuild = function inxbuild() { | |
var i; | |
var j; | |
var smallpos; | |
var smallval; | |
var p; | |
var q; | |
var previouscol; | |
var startpos; | |
previouscol = 0; | |
startpos = 0; | |
for (i = 0; i < netsize; i++) { | |
p = network[i]; | |
smallpos = i; | |
smallval = p[1]; /* index on g */ | |
/* find smallest in i..netsize-1 */ | |
for (j = i + 1; j < netsize; j++) { | |
q = network[j]; | |
if (q[1] < smallval) { /* index on g */ | |
smallpos = j; | |
smallval = q[1]; /* index on g */ | |
} | |
} | |
q = network[smallpos]; | |
/* swap p (i) and q (smallpos) entries */ | |
if (i != smallpos) { | |
j = q[0]; | |
q[0] = p[0]; | |
p[0] = j; | |
j = q[1]; | |
q[1] = p[1]; | |
p[1] = j; | |
j = q[2]; | |
q[2] = p[2]; | |
p[2] = j; | |
j = q[3]; | |
q[3] = p[3]; | |
p[3] = j; | |
} | |
/* smallval entry is now in position i */ | |
if (smallval != previouscol) { | |
netindex[previouscol] = (startpos + i) >> 1; | |
for (j = previouscol + 1; j < smallval; j++) netindex[j] = i; | |
previouscol = smallval; | |
startpos = i; | |
} | |
} | |
netindex[previouscol] = (startpos + maxnetpos) >> 1; | |
for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */ | |
}; | |
/* | |
* Main Learning Loop ------------------ | |
*/ | |
var learn = function learn() { | |
var i; | |
var j; | |
var b; | |
var g; | |
var r; | |
var radius; | |
var rad; | |
var alpha; | |
var step; | |
var delta; | |
var samplepixels; | |
var p; | |
var pix; | |
var lim; | |
if (lengthcount < minpicturebytes) samplefac = 1; | |
alphadec = 30 + ((samplefac - 1) / 3); | |
p = thepicture; | |
pix = 0; | |
lim = lengthcount; | |
samplepixels = lengthcount / (3 * samplefac); | |
delta = (samplepixels / ncycles) | 0; | |
alpha = initalpha; | |
radius = initradius; | |
rad = radius >> radiusbiasshift; | |
if (rad <= 1) rad = 0; | |
for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); | |
if (lengthcount < minpicturebytes) step = 3; | |
else if ((lengthcount % prime1) !== 0) step = 3 * prime1; | |
else { | |
if ((lengthcount % prime2) !== 0) step = 3 * prime2; | |
else { | |
if ((lengthcount % prime3) !== 0) step = 3 * prime3; | |
else step = 3 * prime4; | |
} | |
} | |
i = 0; | |
while (i < samplepixels) { | |
b = (p[pix + 0] & 0xff) << netbiasshift; | |
g = (p[pix + 1] & 0xff) << netbiasshift; | |
r = (p[pix + 2] & 0xff) << netbiasshift; | |
j = contest(b, g, r); | |
altersingle(alpha, j, b, g, r); | |
if (rad !== 0) alterneigh(rad, j, b, g, r); /* alter neighbours */ | |
pix += step; | |
if (pix >= lim) pix -= lengthcount; | |
i++; | |
if (delta === 0) delta = 1; | |
if (i % delta === 0) { | |
alpha -= alpha / alphadec; | |
radius -= radius / radiusdec; | |
rad = radius >> radiusbiasshift; | |
if (rad <= 1) rad = 0; | |
for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); | |
} | |
} | |
}; | |
/* | |
** Search for BGR values 0..255 (after net is unbiased) and return colour | |
* index | |
* ---------------------------------------------------------------------------- | |
*/ | |
var map = exports.map = function map(b, g, r) { | |
var i; | |
var j; | |
var dist; | |
var a; | |
var bestd; | |
var p; | |
var best; | |
bestd = 1000; /* biggest possible dist is 256*3 */ | |
best = -1; | |
i = netindex[g]; /* index on g */ | |
j = i - 1; /* start at netindex[g] and work outwards */ | |
while ((i < netsize) || (j >= 0)) { | |
if (i < netsize) { | |
p = network[i]; | |
dist = p[1] - g; /* inx key */ | |
if (dist >= bestd) i = netsize; /* stop iter */ | |
else { | |
i++; | |
if (dist < 0) dist = -dist; | |
a = p[0] - b; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
a = p[2] - r; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
bestd = dist; | |
best = p[3]; | |
} | |
} | |
} | |
} | |
if (j >= 0) { | |
p = network[j]; | |
dist = g - p[1]; /* inx key - reverse dif */ | |
if (dist >= bestd) j = -1; /* stop iter */ | |
else { | |
j--; | |
if (dist < 0) dist = -dist; | |
a = p[0] - b; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
a = p[2] - r; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
bestd = dist; | |
best = p[3]; | |
} | |
} | |
} | |
} | |
} | |
return (best); | |
}; | |
var process = exports.process = function process() { | |
learn(); | |
unbiasnet(); | |
inxbuild(); | |
return colorMap(); | |
}; | |
/* | |
* Unbias network to give byte values 0..255 and record position i to prepare | |
* for sort | |
* ----------------------------------------------------------------------------------- | |
*/ | |
var unbiasnet = function unbiasnet() { | |
var i; | |
var j; | |
for (i = 0; i < netsize; i++) { | |
network[i][0] >>= netbiasshift; | |
network[i][1] >>= netbiasshift; | |
network[i][2] >>= netbiasshift; | |
network[i][3] = i; /* record colour no */ | |
} | |
}; | |
/* | |
* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in | |
* radpower[|i-j|] | |
* --------------------------------------------------------------------------------- | |
*/ | |
var alterneigh = function alterneigh(rad, i, b, g, r) { | |
var j; | |
var k; | |
var lo; | |
var hi; | |
var a; | |
var m; | |
var p; | |
lo = i - rad; | |
if (lo < -1) lo = -1; | |
hi = i + rad; | |
if (hi > netsize) hi = netsize; | |
j = i + 1; | |
k = i - 1; | |
m = 1; | |
while ((j < hi) || (k > lo)) { | |
a = radpower[m++]; | |
if (j < hi) { | |
p = network[j++]; | |
try { | |
p[0] -= (a * (p[0] - b)) / alpharadbias; | |
p[1] -= (a * (p[1] - g)) / alpharadbias; | |
p[2] -= (a * (p[2] - r)) / alpharadbias; | |
} catch (e) {} // prevents 1.3 miscompilation | |
} | |
if (k > lo) { | |
p = network[k--]; | |
try { | |
p[0] -= (a * (p[0] - b)) / alpharadbias; | |
p[1] -= (a * (p[1] - g)) / alpharadbias; | |
p[2] -= (a * (p[2] - r)) / alpharadbias; | |
} catch (e) {} | |
} | |
} | |
}; | |
/* | |
* Move neuron i towards biased (b,g,r) by factor alpha | |
* ---------------------------------------------------- | |
*/ | |
var altersingle = function altersingle(alpha, i, b, g, r) { | |
/* alter hit neuron */ | |
var n = network[i]; | |
n[0] -= (alpha * (n[0] - b)) / initalpha; | |
n[1] -= (alpha * (n[1] - g)) / initalpha; | |
n[2] -= (alpha * (n[2] - r)) / initalpha; | |
}; | |
/* | |
* Search for biased BGR values ---------------------------- | |
*/ | |
var contest = function contest(b, g, r) { | |
/* finds closest neuron (min dist) and updates freq */ | |
/* finds best neuron (min dist-bias) and returns position */ | |
/* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ | |
/* bias[i] = gamma*((1/netsize)-freq[i]) */ | |
var i; | |
var dist; | |
var a; | |
var biasdist; | |
var betafreq; | |
var bestpos; | |
var bestbiaspos; | |
var bestd; | |
var bestbiasd; | |
var n; | |
bestd = ~ (1 << 31); | |
bestbiasd = bestd; | |
bestpos = -1; | |
bestbiaspos = bestpos; | |
for (i = 0; i < netsize; i++) { | |
n = network[i]; | |
dist = n[0] - b; | |
if (dist < 0) dist = -dist; | |
a = n[1] - g; | |
if (a < 0) a = -a; | |
dist += a; | |
a = n[2] - r; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
bestd = dist; | |
bestpos = i; | |
} | |
biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); | |
if (biasdist < bestbiasd) { | |
bestbiasd = biasdist; | |
bestbiaspos = i; | |
} | |
betafreq = (freq[i] >> betashift); | |
freq[i] -= betafreq; | |
bias[i] += (betafreq << gammashift); | |
} | |
freq[bestpos] += beta; | |
bias[bestpos] -= betagamma; | |
return (bestbiaspos); | |
}; | |
NeuQuant.apply(this, arguments); | |
return exports; | |
}; |
var t, speed = 1.6e-4; | |
var x, y, l = 36, sp = Math.sqrt(2)*l; | |
var N = 12; | |
const buffersPerFrame = 4; | |
const loopLength = 90; | |
let recording = false; | |
let gifRecorder = null; | |
function setup() { | |
createCanvas(960, 480); | |
noStroke(); | |
rectMode(CENTER); | |
N = 4+ceil(.5*width/sp); | |
// N = 1.5; | |
} | |
function ease(q) { | |
return 3*q*q - 2*q*q*q; | |
} | |
function mousePressed() { | |
if(gifRecorder == null) { | |
recording = true; | |
gifRecorder = new p5recorder(loopLength, "squaresturning.gif", 50, 0, buffersPerFrame); | |
} | |
} | |
const bright_c = 250; | |
const dim_c = 32; | |
function draw() { | |
let frameMax = loopLength * buffersPerFrame; | |
let curFrame = frameCount % frameMax; | |
t = map(curFrame, 0, frameMax, 0, 1); | |
push(); | |
translate(width/2, height/2); | |
if (t <= .5) { | |
tt = ease(2*t); | |
if(tt>0.5) { | |
background(bright_c); | |
fill(dim_c); | |
} | |
else { | |
background(dim_c); | |
fill(bright_c); | |
} | |
for (i=-N; i<=N; i++) { | |
for (j=-N; j<=N; j++) { | |
x = i*sp + (tt>.5?.5*sp:0) + (j%2 == 0 ? tt*sp : -tt*sp); | |
y = j*sp + (tt>.5?.5*sp:0); | |
push(); | |
translate(x, y); | |
rotate(HALF_PI*tt*(j%2 == 0 ? 1 : -1)); | |
rect(0, 0, l, l); | |
pop(); | |
} | |
} | |
} | |
else { | |
tt = ease(2*t - 1); | |
if(tt<=0.5) { | |
background(bright_c); | |
fill(dim_c); | |
} | |
else { | |
background(dim_c); | |
fill(bright_c); | |
} | |
push(); | |
rotate(HALF_PI); | |
for (i=-N; i<=N; i++) { | |
for (j=-N; j<=N; j++) { | |
x = (i+.5)*sp + (tt>.5?.5*sp:0) + (j%2 == 0 ? tt*sp : -tt*sp); | |
y = (j+.5)*sp + (tt>.5?.5*sp:0); | |
push(); | |
translate(x, y); | |
rotate(HALF_PI*tt*(j%2 == 0 ? 1 : -1)); | |
rect(0, 0, l, l); | |
pop(); | |
} | |
} | |
pop(); | |
} | |
pop(); | |
if(recording) { | |
gifRecorder.addBuffer(); | |
} | |
} | |
function keyTyped() { | |
if (key == '!') { | |
saveBlocksImages(); | |
} | |
} |
function resetFocusedRandom() { | |
return Math.seedrandom(arguments); | |
} | |
function focusedRandom(min, max, focus, mean) { | |
// console.log("hello") | |
if(max === undefined) { | |
max = min; | |
min = 0; | |
} | |
if(focus === undefined) { | |
focus = 1.0; | |
} | |
if(mean === undefined) { | |
mean = (min + max) / 2.0; | |
} | |
if(focus == 0) { | |
return d3.randomUniform(min, max)(); | |
} | |
else if(focus < 0) { | |
focus = -1 / focus; | |
} | |
let sigma = (max - min) / (2 * focus); | |
let val = d3.randomNormal(mean, sigma)(); | |
if (val >= min && val < max) { | |
return val; | |
} | |
return d3.randomUniform(min, max)(); | |
} |
// note: this file is poorly named - it can generally be ignored. | |
// helper functions below for supporting blocks/purview | |
function saveBlocksImages(doZoom) { | |
if(doZoom == null) { | |
doZoom = false; | |
} | |
// generate 960x500 preview.jpg of entire canvas | |
// TODO: should this be recycled? | |
var offscreenCanvas = document.createElement('canvas'); | |
offscreenCanvas.width = 960; | |
offscreenCanvas.height = 500; | |
var context = offscreenCanvas.getContext('2d'); | |
// background is flat white | |
context.fillStyle="#FFFFFF"; | |
context.fillRect(0, 0, 960, 500); | |
context.drawImage(this.canvas, 0, 0, 960, 500); | |
// save to browser | |
var downloadMime = 'image/octet-stream'; | |
var imageData = offscreenCanvas.toDataURL('image/jpeg'); | |
imageData = imageData.replace('image/jpeg', downloadMime); | |
p5.prototype.downloadFile(imageData, 'preview.jpg', 'jpg'); | |
// generate 230x120 thumbnail.png centered on mouse | |
offscreenCanvas.width = 230; | |
offscreenCanvas.height = 120; | |
// background is flat white | |
context = offscreenCanvas.getContext('2d'); | |
context.fillStyle="#FFFFFF"; | |
context.fillRect(0, 0, 230, 120); | |
if(doZoom) { | |
// pixelDensity does the right thing on retina displays | |
var pd = this._pixelDensity; | |
var sx = pd * mouseX - pd * 230/2; | |
var sy = pd * mouseY - pd * 120/2; | |
var sw = pd * 230; | |
var sh = pd * 120; | |
// bounds checking - just displace if necessary | |
if (sx < 0) { | |
sx = 0; | |
} | |
if (sx > this.canvas.width - sw) { | |
sx = this.canvas.width - sw; | |
} | |
if (sy < 0) { | |
sy = 0; | |
} | |
if (sy > this.canvas.height - sh) { | |
sy = this.canvas.height - sh; | |
} | |
// save to browser | |
context.drawImage(this.canvas, sx, sy, sw, sh, 0, 0, 230, 120); | |
} | |
else { | |
// now scaledown | |
var full_width = this.canvas.width; | |
var full_height = this.canvas.height; | |
context.drawImage(this.canvas, 0, 0, full_width, full_height, 0, 0, 230, 120); | |
} | |
imageData = offscreenCanvas.toDataURL('image/png'); | |
imageData = imageData.replace('image/png', downloadMime); | |
// call this function after 1 second | |
setTimeout(function(){ | |
p5.prototype.downloadFile(imageData, 'thumbnail.png', 'png'); | |
}, 1000); | |
} |
function p5recorder(numFrames, filename, delay, repeat, buffersPerFrame) { | |
this.numFrames = numFrames; | |
// all other arguments are optional | |
if(filename) { | |
this.filename = filename; | |
} | |
else { | |
this.filename = "download.gif"; | |
} | |
if(delay) { | |
this.delay = delay; | |
} | |
else { | |
this.delay = 25; //go to next frame every 25 milliseconds | |
} | |
if(repeat) { | |
this.repeat = repeat; | |
} | |
else { | |
this.repeat = 0; //0 -> loop forever | |
} | |
if(buffersPerFrame) { | |
this.buffersPerFrame = buffersPerFrame; | |
} | |
else { | |
this.buffersPerFrame = 1; | |
} | |
this.encoder = new GIFEncoder(); | |
this.offscreenCanvas = document.createElement('canvas'); | |
this.offscreenCanvas.width = width; | |
this.offscreenCanvas.height = height; | |
this.offscreenContext = this.offscreenCanvas.getContext('2d'); | |
this.framesRecorded = 0; | |
this.buffersRecorded = 0; | |
this.imageAccumulator = null; | |
this.encoder_has_started = false; | |
pixelDensity(1); | |
this.addBuffer = function() { | |
if(!this.encoder_has_started) { | |
this.encoder.setRepeat(this.repeat); | |
this.encoder.setDelay(this.delay); | |
this.encoderResult = this.encoder.start(); | |
this.encoder_has_started = true; | |
} | |
let display_text = "Recording: " + (this.framesRecorded+1) + " / " + this.numFrames; | |
if (this.framesRecorded < this.numFrames) { | |
// background is flat white | |
this.offscreenContext.fillStyle="#FFFFFF"; | |
this.offscreenContext.fillRect(0, 0, width, height); | |
this.offscreenContext.drawImage(canvas, 0, 0, width, height); | |
if (this.buffersPerFrame > 1) { | |
display_text = "Recording: " + (this.buffersRecorded+1) + " / " + this.buffersPerFrame + " : " + (this.framesRecorded+1) + " / " + this.numFrames; | |
// each output image is made up of several input frames averaged together | |
if (this.buffersRecorded == 0) { | |
// initialize a new output Image | |
this.imageAccumulator = new Array(width * height); | |
for (let i=0; i<width*height; i++) { | |
this.imageAccumulator[i] = [0, 0, 0]; | |
} | |
} | |
loadPixels(); | |
for (let i=0; i<pixels.length/4; i++) { | |
// print(i); | |
// print(imageAccumulator[i]) | |
// print(pixels[i]) | |
this.imageAccumulator[i][0] += pixels[i*4+0]; | |
this.imageAccumulator[i][1] += pixels[i*4+1]; | |
this.imageAccumulator[i][2] += pixels[i*4+2]; | |
} | |
this.buffersRecorded = this.buffersRecorded + 1; | |
if(this.buffersRecorded == this.buffersPerFrame) { | |
// record this version and increment framesRecorded | |
loadPixels(); | |
for (let i=0; i<pixels.length/4; i++) { | |
pixels[i*4+0] = int(this.imageAccumulator[i][0] * 1.0/this.buffersPerFrame); | |
pixels[i*4+1] = int(this.imageAccumulator[i][1] * 1.0/this.buffersPerFrame); | |
pixels[i*4+2] = int(this.imageAccumulator[i][2] * 1.0/this.buffersPerFrame); | |
pixels[i*4+3] = 255; | |
} | |
updatePixels(); | |
this.imageAccumulator = null; | |
// reload this version onto the offscreen buffer | |
this.offscreenContext.fillStyle="#FFFFFF"; | |
this.offscreenContext.fillRect(0, 0, width, height); | |
this.offscreenContext.drawImage(canvas, 0, 0, width, height); | |
this.encoder.addFrame(this.offscreenContext); | |
this.framesRecorded = this.framesRecorded + 1; | |
this.buffersRecorded = 0; | |
} | |
} | |
else { | |
this.encoder.addFrame(this.offscreenContext); | |
this.framesRecorded = this.framesRecorded + 1; | |
} | |
if(this.framesRecorded == this.numFrames) { | |
this.encoder.finish(); | |
this.encoder.download(this.filename); | |
} | |
} | |
else { | |
display_text = "Recording: done"; | |
} | |
fill(255, 0, 0); | |
textSize(48); | |
text(display_text, 50, height-20); | |
} | |
} |