From 6f43aa8a01a708dd4b6a4ed2455ab275b78df5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 1 Jul 2022 19:03:47 +0200 Subject: [PATCH 01/61] initial commits! --- lib/empty-example/sketch.js | 18 +++++++- src/image/image.js | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index de6c862644..fe73fb1306 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,7 +1,23 @@ +/* eslint-disable no-unused-vars */ + function setup() { // put setup code here + createCanvas(600, 600); + frameRate(3); } function draw() { // put drawing code here -} \ No newline at end of file + background(20); + // print(frameRate()); + + circle( + 100 * sin(frameCount / 10) + width / 2, + 100 * sin(frameCount / 10) + height / 2, + 10 + ); +} + +function mousePressed() { + createGif('mySketch', 2); +} diff --git a/src/image/image.js b/src/image/image.js index 8260776c13..8bb8394412 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -184,8 +184,97 @@ p5.prototype.saveCanvas = function() { }, mimeType); }; +p5.prototype.createGif = function(...args) { + // process args + + let fileName; + let seconds; + let delay; + // let callback; + + switch (args.length) { + case 2: + fileName = args[0]; + seconds = args[1]; + break; + case 3: + fileName = args[0]; + seconds = args[1]; + delay = args[2]; + break; + default: + fileName = args[0]; + seconds = args[1]; + delay = args[2]; + callback = args[3]; + } + + const makeFrame = p5.prototype._makeFrame; + const cnv = this._curElement.elt; + + let ext = 'png'; + + if (!delay) { + delay = 0; + } + print(fileName, seconds, delay); + + let frameRate = this._frameRate || this._targetFrameRate || 60; + let nFrames = seconds * frameRate; + let nFramesDelay = delay * frameRate; + + // TODO: check if delay and nFrames are correct + /* + Natural behaviour for me is wait for nDelay seconds + and then process the next nFrame seconds. Right now this + waits nDelay seconds but processess nFrames - nDelay seconds. + */ + var count = nFramesDelay; + this.frameCount = count; + // var pImg = new p5.Image(this.width, this.height); + var frameBuffer = []; + noLoop(); + + while (count < nFrames) { + /* we draw the next frame. this is important, since + busy sketches or low end devices might take longer + to render the frame. So we just wait for the frame + to be drawn and immediately save it to a buffer and continue + */ + redraw(); + frameBuffer.push(makeFrame(fileName + count, ext, cnv)); + count++; + console.log(frameCount); + console.log('Processing frame ' + count); + } + print(frameBuffer); + _createGif(); + + // if (callback) { + // callback(frameBuffer); + // } else { + // for (const f of frameBuffer) { + // print(f.imageData, f.filename, f.ext); + // // p5.prototype.downloadFile(f.imageData, f.filename, f.ext); + // } + // } + + // const extension = 'gif'; + // const blob = new Blob([frameBuffer], { + // type: 'image/gif', + // }); + // let pImg = new p5.Image(frameBuffer); + // print(pImg); + // let gif = p5.prototype.saveGif(blob, fileName); + // print(gif); + + print('No loop'); + noLoop(); +}; + p5.prototype.saveGif = function(pImg, filename) { const props = pImg.gifProperties; + console.log(props); //convert loopLimit back into Netscape Block formatting let loopLimit = props.loopLimit; From b19d572dd1442c5145c4d8463374a5c0b44e37cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 3 Jul 2022 13:39:28 +0200 Subject: [PATCH 02/61] proper behaviour for delay and seconds --- lib/empty-example/sketch.js | 2 +- src/image/image.js | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index fe73fb1306..76757e7537 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -19,5 +19,5 @@ function draw() { } function mousePressed() { - createGif('mySketch', 2); + createGif('mySketch', 2, 2); } diff --git a/src/image/image.js b/src/image/image.js index 8bb8394412..0611243011 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -217,11 +217,11 @@ p5.prototype.createGif = function(...args) { if (!delay) { delay = 0; } - print(fileName, seconds, delay); let frameRate = this._frameRate || this._targetFrameRate || 60; - let nFrames = seconds * frameRate; - let nFramesDelay = delay * frameRate; + let nFrames = ceil(seconds * frameRate); + let nFramesDelay = ceil(delay * frameRate); + print(frameRate, nFrames, nFramesDelay); // TODO: check if delay and nFrames are correct /* @@ -235,7 +235,7 @@ p5.prototype.createGif = function(...args) { var frameBuffer = []; noLoop(); - while (count < nFrames) { + while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since busy sketches or low end devices might take longer to render the frame. So we just wait for the frame @@ -244,11 +244,9 @@ p5.prototype.createGif = function(...args) { redraw(); frameBuffer.push(makeFrame(fileName + count, ext, cnv)); count++; - console.log(frameCount); - console.log('Processing frame ' + count); } print(frameBuffer); - _createGif(); + // _createGif(); // if (callback) { // callback(frameBuffer); @@ -261,12 +259,12 @@ p5.prototype.createGif = function(...args) { // const extension = 'gif'; // const blob = new Blob([frameBuffer], { - // type: 'image/gif', + // type: 'image/gif' // }); - // let pImg = new p5.Image(frameBuffer); - // print(pImg); - // let gif = p5.prototype.saveGif(blob, fileName); - // print(gif); + let pImg = new p5.Image(frameBuffer); + print(pImg); + let gif = p5.prototype.saveGif(blob, fileName); + print(gif); print('No loop'); noLoop(); From 228ae550e4e7364732360eef4459138d54042a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 3 Jul 2022 16:00:42 +0200 Subject: [PATCH 03/61] change function from image.js to loading_displaying.js --- src/image/image.js | 87 ------------------------------- src/image/loading_displaying.js | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 87 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index 0611243011..8260776c13 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -184,95 +184,8 @@ p5.prototype.saveCanvas = function() { }, mimeType); }; -p5.prototype.createGif = function(...args) { - // process args - - let fileName; - let seconds; - let delay; - // let callback; - - switch (args.length) { - case 2: - fileName = args[0]; - seconds = args[1]; - break; - case 3: - fileName = args[0]; - seconds = args[1]; - delay = args[2]; - break; - default: - fileName = args[0]; - seconds = args[1]; - delay = args[2]; - callback = args[3]; - } - - const makeFrame = p5.prototype._makeFrame; - const cnv = this._curElement.elt; - - let ext = 'png'; - - if (!delay) { - delay = 0; - } - - let frameRate = this._frameRate || this._targetFrameRate || 60; - let nFrames = ceil(seconds * frameRate); - let nFramesDelay = ceil(delay * frameRate); - print(frameRate, nFrames, nFramesDelay); - - // TODO: check if delay and nFrames are correct - /* - Natural behaviour for me is wait for nDelay seconds - and then process the next nFrame seconds. Right now this - waits nDelay seconds but processess nFrames - nDelay seconds. - */ - var count = nFramesDelay; - this.frameCount = count; - // var pImg = new p5.Image(this.width, this.height); - var frameBuffer = []; - noLoop(); - - while (count < nFrames + nFramesDelay) { - /* we draw the next frame. this is important, since - busy sketches or low end devices might take longer - to render the frame. So we just wait for the frame - to be drawn and immediately save it to a buffer and continue - */ - redraw(); - frameBuffer.push(makeFrame(fileName + count, ext, cnv)); - count++; - } - print(frameBuffer); - // _createGif(); - - // if (callback) { - // callback(frameBuffer); - // } else { - // for (const f of frameBuffer) { - // print(f.imageData, f.filename, f.ext); - // // p5.prototype.downloadFile(f.imageData, f.filename, f.ext); - // } - // } - - // const extension = 'gif'; - // const blob = new Blob([frameBuffer], { - // type: 'image/gif' - // }); - let pImg = new p5.Image(frameBuffer); - print(pImg); - let gif = p5.prototype.saveGif(blob, fileName); - print(gif); - - print('No loop'); - noLoop(); -}; - p5.prototype.saveGif = function(pImg, filename) { const props = pImg.gifProperties; - console.log(props); //convert loopLimit back into Netscape Block formatting let loopLimit = props.loopLimit; diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 3ff2b3f053..7c374b644d 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -159,6 +159,97 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { return pImg; }; +p5.prototype.createGif = function(...args) { + // process args + + let fileName; + let seconds; + let delay; + // let callback; + + switch (args.length) { + case 2: + fileName = args[0]; + seconds = args[1]; + break; + case 3: + fileName = args[0]; + seconds = args[1]; + delay = args[2]; + break; + default: + fileName = args[0]; + seconds = args[1]; + delay = args[2]; + callback = args[3]; + } + + // const makeFrame = p5.prototype._makeFrame; + // const cnv = this._curElement.elt; + + // let ext = 'png'; + + if (!delay) { + delay = 0; + } + + let frameRate = this._frameRate || this._targetFrameRate || 60; + let nFrames = ceil(seconds * frameRate); + let nFramesDelay = ceil(delay * frameRate); + print(frameRate, nFrames, nFramesDelay); + + var count = nFramesDelay; + this.frameCount = count; + + // var frameBuffer = new Uint8Array(this.width * this.height * 4 * nFrames); + var pImg = new p5.Image(this.width, this.height, this); + + noLoop(); + + // var loopLimit = 0; //loops forever + // pImg.gifProperties = { + // displayIndex: 0, + // loopLimit, + // loopCount: 0, + // nFrames, + // playing: true, + // timeDisplayed: 0, + // lastChangeTime: 0 + // }; + + while (count < nFrames + nFramesDelay) { + /* + we draw the next frame. this is important, since + busy sketches or low end devices might take longer + to render the frame. So we just wait for the frame + to be drawn and immediately save it to a buffer and continue + */ + redraw(); + // frameBuffer.push(makeFrame(fileName + count, ext, cnv)); + // frameBuffer; + let framePixels = this.drawingContext.getImageData( + 0, + 0, + this.width, + this.height + ).data; + print(framePixels); + const imageData = new ImageData(framePixels, pImg.width, pImg.height); + pImg.drawingContext.putImageData(imageData, 0, 0); + + count++; + print('Processing frame: ' + count); + print('Frame count: ' + this.frameCount); + } + + print(pImg); + saveGif(pImg, fileName); + + print('No loop'); + noLoop(); + frameBuffer = []; +}; + /** * Helper function for loading GIF-based images */ From 98be55f3ac4487c28452957c8a8e93a70a409bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 3 Jul 2022 17:04:54 +0200 Subject: [PATCH 04/61] change naming scheme --- lib/empty-example/sketch.js | 2 +- src/image/image.js | 2 +- src/image/loading_displaying.js | 66 +++++++++++++++++---------------- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 76757e7537..dc942874b9 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -19,5 +19,5 @@ function draw() { } function mousePressed() { - createGif('mySketch', 2, 2); + saveGif('mySketch', 2, 2); } diff --git a/src/image/image.js b/src/image/image.js index 8260776c13..6a3e9fa410 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -184,7 +184,7 @@ p5.prototype.saveCanvas = function() { }, mimeType); }; -p5.prototype.saveGif = function(pImg, filename) { +p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const props = pImg.gifProperties; //convert loopLimit back into Netscape Block formatting diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 7c374b644d..3ae982b390 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -159,8 +159,9 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { return pImg; }; -p5.prototype.createGif = function(...args) { +p5.prototype.saveGif = function(...args) { // process args + // let fileName; let seconds; @@ -184,11 +185,6 @@ p5.prototype.createGif = function(...args) { callback = args[3]; } - // const makeFrame = p5.prototype._makeFrame; - // const cnv = this._curElement.elt; - - // let ext = 'png'; - if (!delay) { delay = 0; } @@ -201,22 +197,15 @@ p5.prototype.createGif = function(...args) { var count = nFramesDelay; this.frameCount = count; - // var frameBuffer = new Uint8Array(this.width * this.height * 4 * nFrames); - var pImg = new p5.Image(this.width, this.height, this); + // width * height * (r,g,b,a) * frames + // var frameBuffer = new Uint8ClampedArray( + // this.width * this.height * 4 * nFrames + // ); + let frames = []; + let pImg = new p5.Image(this.width, this.height, this); noLoop(); - // var loopLimit = 0; //loops forever - // pImg.gifProperties = { - // displayIndex: 0, - // loopLimit, - // loopCount: 0, - // nFrames, - // playing: true, - // timeDisplayed: 0, - // lastChangeTime: 0 - // }; - while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -225,29 +214,42 @@ p5.prototype.createGif = function(...args) { to be drawn and immediately save it to a buffer and continue */ redraw(); - // frameBuffer.push(makeFrame(fileName + count, ext, cnv)); - // frameBuffer; - let framePixels = this.drawingContext.getImageData( + + let frameData = this.drawingContext.getImageData( 0, 0, this.width, this.height - ).data; - print(framePixels); - const imageData = new ImageData(framePixels, pImg.width, pImg.height); - pImg.drawingContext.putImageData(imageData, 0, 0); + ); + + pImg.drawingContext.putImageData(frameData, 0, 0); + + frames.push({ + image: pImg.drawingContext.getImageData(0, 0, pImg.width, pImg.height), + delay: 10 * 10 //GIF stores delay in one-hundredth of a second, shift to ms + }); count++; + print('Processing frame: ' + count); print('Frame count: ' + this.frameCount); } - print(pImg); - saveGif(pImg, fileName); - - print('No loop'); - noLoop(); - frameBuffer = []; + pImg.drawingContext.putImageData(frames[0].image, 0, 0); + pImg.gifProperties = { + displayIndex: 0, + loopLimit: 0, // let it loop indefinitely + loopCount: 0, + frames: frames, + numFrames: nFrames, + playing: true, + timeDisplayed: 0, + lastChangeTime: 0 + }; + + p5.prototype.encodeAndDownloadGif(pImg, fileName); + + frames = []; }; /** From f84cedc894629e9f87c9b36db18e5260bb9dcbbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 3 Jul 2022 17:35:27 +0200 Subject: [PATCH 05/61] we are now able to dowload gifs! --- lib/empty-example/sketch.js | 3 +-- src/image/loading_displaying.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index dc942874b9..635854228c 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -3,7 +3,6 @@ function setup() { // put setup code here createCanvas(600, 600); - frameRate(3); } function draw() { @@ -19,5 +18,5 @@ function draw() { } function mousePressed() { - saveGif('mySketch', 2, 2); + saveGif('mySketch', 5); } diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 3ae982b390..b9e446b792 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -195,7 +195,7 @@ p5.prototype.saveGif = function(...args) { print(frameRate, nFrames, nFramesDelay); var count = nFramesDelay; - this.frameCount = count; + // this.frameCount = count; // width * height * (r,g,b,a) * frames // var frameBuffer = new Uint8ClampedArray( @@ -226,7 +226,7 @@ p5.prototype.saveGif = function(...args) { frames.push({ image: pImg.drawingContext.getImageData(0, 0, pImg.width, pImg.height), - delay: 10 * 10 //GIF stores delay in one-hundredth of a second, shift to ms + delay: 2 / 100 //GIF stores delay in one-hundredth of a second, shift to ms }); count++; From 99d1a0e1b0a31732fe8a228b9627b4e8871ac605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 4 Jul 2022 18:05:20 +0200 Subject: [PATCH 06/61] create documentation, example and fix bug --- lib/empty-example/sketch.js | 10 ++-- src/image/loading_displaying.js | 82 +++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 635854228c..42f8194355 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,13 +1,13 @@ /* eslint-disable no-unused-vars */ function setup() { - // put setup code here - createCanvas(600, 600); + createCanvas(100, 100); + colorMode(HSL); } function draw() { - // put drawing code here - background(20); + let hue = map(sin(frameCount / 100), -1, 1, 0, 100); + background(hue, 40, 60); // print(frameRate()); circle( @@ -18,5 +18,5 @@ function draw() { } function mousePressed() { - saveGif('mySketch', 5); + saveGif('mySketch', 2); } diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index b9e446b792..cc9ed18f06 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -159,6 +159,54 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { return pImg; }; +/** + * Generates a gif of your current animation and downloads it to your computer! + * + * The duration argument specifies how many seconds you want to record from your animation. + * This value is then converted to the necessary number of frames to generate it. + * + * With the delay argument, you can tell the function to skip the first `delay` seconds + * of the animation, and then download the `duration` next seconds. This means that regardless + * of the value of `delay`, your gif will always be `duration` seconds long. + * + * @method saveGif + * @param {String} filename File name of your gif + * @param {String} duration Duration in seconds that you wish to capture from your sketch + * @param {String} delay Duration in seconds that you wish wait before starting to capture + * + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * colorMode(HSL); + * } + + * function draw() { + * // create some cool dynamic background + * let hue = map(sin(frameCount / 100), -1, 1, 0, 100); + * background(hue, 40, 60); + + * // create a circle that moves diagonally + * circle( + * 100 * sin(frameCount / 10) + width / 2, + * 100 * sin(frameCount / 10) + height / 2, + * 10 + * ); + * } + + * // you can put it in the mousePressed function, + * // or keyPressed for example + * function mousePressed() { + * // this will download the first two seconds of my animation! + * saveGif('mySketch', 2); + * } + * + *
+ * + * @alt + * image of the underside of a white umbrella and grided ceililng above + */ p5.prototype.saveGif = function(...args) { // process args // @@ -166,7 +214,6 @@ p5.prototype.saveGif = function(...args) { let fileName; let seconds; let delay; - // let callback; switch (args.length) { case 2: @@ -178,34 +225,28 @@ p5.prototype.saveGif = function(...args) { seconds = args[1]; delay = args[2]; break; - default: - fileName = args[0]; - seconds = args[1]; - delay = args[2]; - callback = args[3]; } if (!delay) { delay = 0; } - let frameRate = this._frameRate || this._targetFrameRate || 60; - let nFrames = ceil(seconds * frameRate); - let nFramesDelay = ceil(delay * frameRate); - print(frameRate, nFrames, nFramesDelay); + let _frameRate = this._frameRate || this._targetFrameRate || 60; + let nFrames = Math.ceil(seconds * _frameRate); + let nFramesDelay = Math.ceil(delay * _frameRate); + print(_frameRate, nFrames, nFramesDelay); var count = nFramesDelay; - // this.frameCount = count; - // width * height * (r,g,b,a) * frames - // var frameBuffer = new Uint8ClampedArray( - // this.width * this.height * 4 * nFrames - // ); let frames = []; let pImg = new p5.Image(this.width, this.height, this); noLoop(); + console.log( + 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' + ); + while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -225,14 +266,11 @@ p5.prototype.saveGif = function(...args) { pImg.drawingContext.putImageData(frameData, 0, 0); frames.push({ - image: pImg.drawingContext.getImageData(0, 0, pImg.width, pImg.height), - delay: 2 / 100 //GIF stores delay in one-hundredth of a second, shift to ms + image: pImg.drawingContext.getImageData(0, 0, this.width, this.height), + delay: 100 //GIF stores delay in one-hundredth of a second, shift to ms }); count++; - - print('Processing frame: ' + count); - print('Frame count: ' + this.frameCount); } pImg.drawingContext.putImageData(frames[0].image, 0, 0); @@ -247,8 +285,12 @@ p5.prototype.saveGif = function(...args) { lastChangeTime: 0 }; + console.info('Frames processed, encoding gif. This may take a while...'); + p5.prototype.encodeAndDownloadGif(pImg, fileName); + loop(); + frames = []; }; From 4f0b3f65c663d5dca755526ce352a3f5b5012a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 8 Jul 2022 09:52:46 +0200 Subject: [PATCH 07/61] latest changes --- lib/empty-example/sketch.js | 224 +++++++++++++++++++++++++++++++- src/image/image.js | 1 - src/image/loading_displaying.js | 15 ++- 3 files changed, 227 insertions(+), 13 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 42f8194355..3ed12e63bc 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,22 +1,234 @@ /* eslint-disable no-unused-vars */ function setup() { - createCanvas(100, 100); - colorMode(HSL); + // put setup code here + createCanvas(300, 300); } function draw() { + // put drawing code here let hue = map(sin(frameCount / 100), -1, 1, 0, 100); - background(hue, 40, 60); + background(20); // print(frameRate()); + fill(250); circle( - 100 * sin(frameCount / 10) + width / 2, - 100 * sin(frameCount / 10) + height / 2, - 10 + 100 * sin(frameCount / 70) + width / 2, + 100 * sin(frameCount / 70) + height / 2, + 100 ); } function mousePressed() { saveGif('mySketch', 2); } + +/// COMPLEX SKETCH +// let offset; +// let spacing; + +// // let font; +// // function preload() { +// // font = loadFont("../SF-Mono-Regular.otf"); +// // } + +// function setup() { +// randomSeed(122); + +// w = min(windowHeight, windowWidth); +// createCanvas(w, w); + +// looping = false; +// saving = false; +// noLoop(); + +// divisor = random(1.2, 3).toFixed(2); + +// frameWidth = w / divisor; +// offset = (-frameWidth + w) / 2; + +// gen_num_total_squares = int(random(2, 20)); +// spacing = frameWidth / gen_num_total_squares; + +// initHue = random(0, 360); +// compColor = (initHue + 360 / random(1, 4)) % 360; + +// gen_stroke_weight = random(-100, 100); +// gen_stroke_fade_speed = random(30, 150); +// gen_shift_small_squares = random(0, 10); + +// gen_offset_small_sq_i = random(3, 10); +// gen_offset_small_sq_j = random(3, 10); + +// gen_rotation_speed = random(30, 250); + +// gen_depth = random(5, 20); +// gen_offset_i = random(1, 10); +// gen_offset_j = random(1, 10); + +// gen_transparency = random(20, 255); + +// background(24); +// } + +// function draw() { +// colorMode(HSB); +// background(initHue, 80, 20, gen_transparency); +// makeSquares(); +// // addHandle(); + +// if (saving) save('grid' + frameCount + '.png'); +// } + +// function makeSquares(depth = gen_depth) { +// colorMode(HSB); +// let count_i = 0; + +// for (let i = offset; i < w - offset; i += spacing) { +// let count_j = 0; +// count_i++; + +// if (count_i > gen_num_total_squares) break; + +// for (let j = offset; j < w - offset; j += spacing) { +// count_j++; + +// if (count_j > gen_num_total_squares) break; + +// for (let n = 0; n < depth; n++) { +// noFill(); + +// if (n === 0) { +// stroke(initHue, 100, 100); +// fill( +// initHue, +// 100, +// 100, +// map( +// sin( +// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed +// ), +// -1, +// 1, +// 0, +// 0.3 +// ) +// ); +// } else { +// stroke(compColor, map(n, 0, depth, 100, 0), 100); +// fill( +// compColor, +// 100, +// 100, +// map( +// cos( +// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed +// ), +// -1, +// 1, +// 0, +// 0.3 +// ) +// ); +// } + +// strokeWeight( +// map( +// sin( +// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed +// ), +// -1, +// 1, +// 0, +// 1.5 +// ) +// ); + +// push(); +// translate(i + spacing / 2, j + spacing / 2); + +// rotate( +// i * gen_offset_i + +// j * gen_offset_j + +// frameCount / (gen_rotation_speed / (n + 1)) +// ); + +// if (n % 2 !== 0) { +// translate( +// sin(frameCount / 50) * gen_shift_small_squares, +// cos(frameCount / 50) * gen_shift_small_squares +// ); +// rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); +// } + +// if (n > 0) +// rect( +// -spacing / (gen_offset_small_sq_i + n), +// -spacing / (gen_offset_small_sq_j + n), +// spacing / (n + 1), +// spacing / (n + 1) +// ); +// else rect(-spacing / 2, -spacing / 2, spacing, spacing); + +// pop(); +// } +// // strokeWeight(40); +// // point(i, j); +// } +// } +// } + +// function addHandle() { +// fill(40); +// noStroke(); +// textAlign(RIGHT, BOTTOM); +// textFont(font); +// textSize(20); +// text('@jesi_rgb', w - 30, w - 30); +// } + +// function mousePressed() { +// if (mouseButton === LEFT) { +// if (looping) { +// noLoop(); +// looping = false; +// } else { +// loop(); +// looping = true; +// } +// } +// } + +// function keyPressed() { +// console.log(key); +// switch (key) { +// // pressing the 's' key +// case 's': +// saveGif('mySketch', 2); +// break; + +// // pressing the '0' key +// case '0': +// frameCount = 0; +// loop(); +// noLoop(); +// break; + +// // pressing the ← key +// case 'ArrowLeft': +// frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); +// noLoop(); +// console.log(frameCount); +// break; + +// // pressing the → key +// case 'ArrowRights': +// frameCount += 1; +// noLoop(); +// console.log(frameCount); +// break; + +// default: +// break; +// } +// } diff --git a/src/image/image.js b/src/image/image.js index 6a3e9fa410..e2e6ef9954 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -302,7 +302,6 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { }; const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts); let previousFrame = {}; - // Pass 2 // Determine if the frame needs a local palette // Also apply transparency optimization. This function will often blow up diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index cc9ed18f06..97ff0bed0f 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -231,7 +231,10 @@ p5.prototype.saveGif = function(...args) { delay = 0; } - let _frameRate = this._frameRate || this._targetFrameRate || 60; + let _frameRate = this._frameRate || this._targetFrameRate; + if (_frameRate === Infinity || _frameRate === undefined || _frameRate === 0) { + _frameRate = 60; + } let nFrames = Math.ceil(seconds * _frameRate); let nFramesDelay = Math.ceil(delay * _frameRate); print(_frameRate, nFrames, nFramesDelay); @@ -259,21 +262,21 @@ p5.prototype.saveGif = function(...args) { let frameData = this.drawingContext.getImageData( 0, 0, - this.width, - this.height + this.width * 2, + this.height * 2 ); - pImg.drawingContext.putImageData(frameData, 0, 0); + // pImg.drawingContext.putImageData(frameData, 0, 0); frames.push({ - image: pImg.drawingContext.getImageData(0, 0, this.width, this.height), + image: frameData, delay: 100 //GIF stores delay in one-hundredth of a second, shift to ms }); count++; } - pImg.drawingContext.putImageData(frames[0].image, 0, 0); + // pImg.drawingContext.putImageData(frames[0].image, 0, 0); pImg.gifProperties = { displayIndex: 0, loopLimit: 0, // let it loop indefinitely From 9f066e08cc06730ec39ea420ecda720c32431419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 14 Jul 2022 09:30:06 +0200 Subject: [PATCH 08/61] solved problem with framerate --- src/image/loading_displaying.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 97ff0bed0f..c84b7ce8f1 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -262,15 +262,15 @@ p5.prototype.saveGif = function(...args) { let frameData = this.drawingContext.getImageData( 0, 0, - this.width * 2, - this.height * 2 + this.width, + this.height ); // pImg.drawingContext.putImageData(frameData, 0, 0); frames.push({ image: frameData, - delay: 100 //GIF stores delay in one-hundredth of a second, shift to ms + delay: 20 // 20 (which will then be converted to 2 inside the decoding function) is the minimum value that will work }); count++; From fb8af2d818e9ba36cd74a94f67566eb23e6ae3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 15 Jul 2022 09:57:47 +0200 Subject: [PATCH 09/61] adding comments to saveGif function --- src/image/loading_displaying.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index c84b7ce8f1..9c74198db6 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -172,7 +172,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * @method saveGif * @param {String} filename File name of your gif * @param {String} duration Duration in seconds that you wish to capture from your sketch - * @param {String} delay Duration in seconds that you wish wait before starting to capture + * @param {String} delay Duration in seconds that you wish to wait before starting to capture * * @example *
@@ -209,12 +209,13 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { */ p5.prototype.saveGif = function(...args) { // process args - // let fileName; let seconds; let delay; + // this section takes care of parsing and processing + // the arguments in the correct format switch (args.length) { case 2: fileName = args[0]; @@ -231,20 +232,26 @@ p5.prototype.saveGif = function(...args) { delay = 0; } + // get the project's framerate + // if it is undefined or some non useful value, assume it's 60 let _frameRate = this._frameRate || this._targetFrameRate; if (_frameRate === Infinity || _frameRate === undefined || _frameRate === 0) { _frameRate = 60; } + + // because the input was in seconds, we now calculate + // how many frames those seconds translate to let nFrames = Math.ceil(seconds * _frameRate); let nFramesDelay = Math.ceil(delay * _frameRate); - print(_frameRate, nFrames, nFramesDelay); + // initialize variables for the frames processing var count = nFramesDelay; - let frames = []; let pImg = new p5.Image(this.width, this.height, this); noLoop(); + // we start on the frame set by the delay argument + frameCount = nFramesDelay; console.log( 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' @@ -254,7 +261,7 @@ p5.prototype.saveGif = function(...args) { /* we draw the next frame. this is important, since busy sketches or low end devices might take longer - to render the frame. So we just wait for the frame + to render some frames. So we just wait for the frame to be drawn and immediately save it to a buffer and continue */ redraw(); @@ -266,17 +273,17 @@ p5.prototype.saveGif = function(...args) { this.height ); - // pImg.drawingContext.putImageData(frameData, 0, 0); - frames.push({ image: frameData, - delay: 20 // 20 (which will then be converted to 2 inside the decoding function) is the minimum value that will work + delay: 20 + // 20 (which will then be converted to 2 inside the decoding function) + // is the minimum value that will work. Browsers will simply ignore + // values of 1. This is the smoothest GIF possible. }); count++; } - // pImg.drawingContext.putImageData(frames[0].image, 0, 0); pImg.gifProperties = { displayIndex: 0, loopLimit: 0, // let it loop indefinitely @@ -292,9 +299,9 @@ p5.prototype.saveGif = function(...args) { p5.prototype.encodeAndDownloadGif(pImg, fileName); - loop(); - frames = []; + + loop(); }; /** From 7f158a78b071c5bd39d742cf08aea92dbee6e375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 15 Jul 2022 09:58:18 +0200 Subject: [PATCH 10/61] adding constrain to powof2 to match the requirements of omggif --- src/image/image.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index e2e6ef9954..36f47ebacc 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -293,7 +293,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { while (powof2 < globalPalette.length) { powof2 <<= 1; } - globalPalette.length = powof2; + globalPalette.length = constrain(powof2, 2, 256); // global opts const opts = { @@ -370,7 +370,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { while (powof2 < palette.length) { powof2 <<= 1; } - palette.length = powof2; + palette.length = constrain(powof2, 2, 256); frameOpts.palette = new Uint32Array(palette); } if (i > 0) { From 2674e40248536efef623c177ab9000a4f6586990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 15 Jul 2022 09:58:40 +0200 Subject: [PATCH 11/61] some changes to sketch [not important] --- lib/empty-example/index.html | 1 + lib/empty-example/sketch.js | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/empty-example/index.html b/lib/empty-example/index.html index e8e44a5b0b..65f5f34363 100644 --- a/lib/empty-example/index.html +++ b/lib/empty-example/index.html @@ -9,6 +9,7 @@ body { padding: 0; margin: 0; + background-color: black; } diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 3ed12e63bc..3f9232f212 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -2,14 +2,16 @@ function setup() { // put setup code here - createCanvas(300, 300); + createCanvas(200, 200); } function draw() { // put drawing code here let hue = map(sin(frameCount / 100), -1, 1, 0, 100); - background(20); - // print(frameRate()); + background(hue); + + line(width / 2, 0, width / 2, height); + line(0, height / 2, width, height / 2); fill(250); circle( @@ -20,10 +22,12 @@ function draw() { } function mousePressed() { - saveGif('mySketch', 2); + if (mouseButton === RIGHT) { + saveGif('mySketch', 2, 3); + } } -/// COMPLEX SKETCH +// / COMPLEX SKETCH // let offset; // let spacing; @@ -33,7 +37,7 @@ function mousePressed() { // // } // function setup() { -// randomSeed(122); +// randomSeed(125); // w = min(windowHeight, windowWidth); // createCanvas(w, w); From 99def1eaf0d8f75c93f93cbd0b716411c8ff0993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 15 Jul 2022 20:39:05 +0200 Subject: [PATCH 12/61] fixed local palette being greater than 256 colors --- src/image/image.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/image/image.js b/src/image/image.js index 36f47ebacc..618aa58f15 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -310,6 +310,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { // transparent. We decide one particular color as transparent and make all // transparent pixels take this color. This helps in later in compression. for (let i = 0; i < props.numFrames; i++) { + print('FRAME:' + i.toString()); const localPaletteRequired = !framesUsingGlobalPalette.has(i); const palette = localPaletteRequired ? [] : globalPalette; const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height); @@ -323,7 +324,8 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { for (let k = 0; k < allFramesPixelColors[i].length; k++) { const color = allFramesPixelColors[i][k]; if (localPaletteRequired) { - if (colorIndicesLookup[color] === undefined) { + // local palette cannot be greater than 256 colors + if (colorIndicesLookup[color] === undefined && palette.length <= 256) { colorIndicesLookup[color] = palette.length; palette.push(color); } @@ -345,12 +347,15 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { // Transparency optimization const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a)); + print(canBeTransparent, canBeTransparent.length > 0); if (canBeTransparent.length > 0) { // Select a color to mark as transparent const transparent = canBeTransparent[0]; + print(localPaletteRequired ? 'local' : 'global'); const transparentIndex = localPaletteRequired ? colorIndicesLookup[transparent] : globalIndicesLookup[transparent]; + print(transparent, transparentIndex); if (i > 0) { for (let k = 0; k < allFramesPixelColors[i].length; k++) { // If this pixel in this frame has the same color in previous frame @@ -358,11 +363,13 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { pixelPaletteIndex[k] = transparentIndex; } } + frameOpts.transparent = transparentIndex; // If this frame has any transparency, do not dispose the previous frame previousFrame.frameOpts.disposal = 1; } } + frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting if (localPaletteRequired) { // force palette to be power of 2 @@ -375,6 +382,10 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { } if (i > 0) { // add the frame that came before the current one + // print('FRAME: ' + i.toString()); + print(previousFrame.frameOpts); + // print(''); + // print(''); gifWriter.addFrame( 0, 0, From f7c3a87b0d58fced2852d1a758a2226ac3159901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 15 Jul 2022 20:56:53 +0200 Subject: [PATCH 13/61] palette is now properly sized --- src/image/image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image/image.js b/src/image/image.js index 618aa58f15..7273796bf0 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -325,7 +325,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const color = allFramesPixelColors[i][k]; if (localPaletteRequired) { // local palette cannot be greater than 256 colors - if (colorIndicesLookup[color] === undefined && palette.length <= 256) { + if (colorIndicesLookup[color] === undefined && palette.length <= 255) { colorIndicesLookup[color] = palette.length; palette.push(color); } From 0decfc05b011ebc2c08bd84acbe5c9efebc7dd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 15 Jul 2022 20:57:13 +0200 Subject: [PATCH 14/61] minor changes to follow the project's structure --- src/image/loading_displaying.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 9c74198db6..e0b79e2ee1 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -205,7 +205,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { *
* * @alt - * image of the underside of a white umbrella and grided ceililng above + * animation of a circle moving smoothly diagonally */ p5.prototype.saveGif = function(...args) { // process args @@ -234,7 +234,7 @@ p5.prototype.saveGif = function(...args) { // get the project's framerate // if it is undefined or some non useful value, assume it's 60 - let _frameRate = this._frameRate || this._targetFrameRate; + let _frameRate = this._targetFrameRate; if (_frameRate === Infinity || _frameRate === undefined || _frameRate === 0) { _frameRate = 60; } @@ -247,7 +247,7 @@ p5.prototype.saveGif = function(...args) { // initialize variables for the frames processing var count = nFramesDelay; let frames = []; - let pImg = new p5.Image(this.width, this.height, this); + let pImg = new p5.Image(this.width, this.height); noLoop(); // we start on the frame set by the delay argument @@ -257,6 +257,8 @@ p5.prototype.saveGif = function(...args) { 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' ); + let framePixels = new Uint8ClampedArray(this.width * this.height * 4); + while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -266,24 +268,35 @@ p5.prototype.saveGif = function(...args) { */ redraw(); - let frameData = this.drawingContext.getImageData( + const prevFrameData = this.drawingContext.getImageData( 0, 0, this.width, this.height ); + framePixels = prevFrameData.data; + + const imageData = new ImageData(framePixels, pImg.width, pImg.height); + pImg.drawingContext.putImageData(imageData, 0, 0); frames.push({ - image: frameData, + image: prevFrameData, delay: 20 - // 20 (which will then be converted to 2 inside the decoding function) - // is the minimum value that will work. Browsers will simply ignore - // values of 1. This is the smoothest GIF possible. }); + // frames.push({ + // image: framePixels, + // delay: 20 + // // 20 (which will then be converted to 2 inside the decoding function) + // // is the minimum value that will work. Browsers will simply ignore + // // values of 1. This is the smoothest GIF possible. + // }); + count++; } + print(frames[1]); + pImg.gifProperties = { displayIndex: 0, loopLimit: 0, // let it loop indefinitely @@ -297,11 +310,9 @@ p5.prototype.saveGif = function(...args) { console.info('Frames processed, encoding gif. This may take a while...'); - p5.prototype.encodeAndDownloadGif(pImg, fileName); - frames = []; - loop(); + p5.prototype.encodeAndDownloadGif(pImg, fileName); }; /** From 8d1d93490b67be9780eec9e3ece9e4a70f2146a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sat, 16 Jul 2022 12:27:36 +0200 Subject: [PATCH 15/61] mayor bugfix: we now save the whole canvas! --- src/image/loading_displaying.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index e0b79e2ee1..3a355439a6 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -247,7 +247,6 @@ p5.prototype.saveGif = function(...args) { // initialize variables for the frames processing var count = nFramesDelay; let frames = []; - let pImg = new p5.Image(this.width, this.height); noLoop(); // we start on the frame set by the delay argument @@ -257,7 +256,10 @@ p5.prototype.saveGif = function(...args) { 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' ); - let framePixels = new Uint8ClampedArray(this.width * this.height * 4); + const pd = this._pixelDensity; + const width_pd = this.width * pd; + const height_pd = this.height * pd; + let pImg = new p5.Image(width_pd, height_pd); while (count < nFrames + nFramesDelay) { /* @@ -271,27 +273,15 @@ p5.prototype.saveGif = function(...args) { const prevFrameData = this.drawingContext.getImageData( 0, 0, - this.width, - this.height + width_pd, + height_pd ); - framePixels = prevFrameData.data; - - const imageData = new ImageData(framePixels, pImg.width, pImg.height); - pImg.drawingContext.putImageData(imageData, 0, 0); frames.push({ image: prevFrameData, delay: 20 }); - // frames.push({ - // image: framePixels, - // delay: 20 - // // 20 (which will then be converted to 2 inside the decoding function) - // // is the minimum value that will work. Browsers will simply ignore - // // values of 1. This is the smoothest GIF possible. - // }); - count++; } From 6928988db407296a14856e98915a528b714fe2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Tue, 19 Jul 2022 14:04:55 +0200 Subject: [PATCH 16/61] sync commit --- src/image/image.js | 17 ++++++++--------- src/image/loading_displaying.js | 3 +-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index 7273796bf0..b0fa6074f5 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -265,10 +265,13 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const difference = palette.filter(x => !globalPaletteSet.has(x)); if (globalPalette.length + difference.length <= 256) { - for (let j = 0; j < difference.length; j++) { - globalPalette.push(difference[j]); - globalPaletteSet.add(difference[j]); - } + print(globalPalette.length); + globalPalette.concat(difference); + difference.forEach(v => globalPaletteSet.add(v)); + // for (let j = 0; j < difference.length; j++) { + // // globalPalette.push(difference[j]); + // globalPaletteSet.add(difference[j]); + // } // All frames using this palette now use the global palette framesUsingGlobalPalette = framesUsingGlobalPalette.concat( @@ -310,7 +313,6 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { // transparent. We decide one particular color as transparent and make all // transparent pixels take this color. This helps in later in compression. for (let i = 0; i < props.numFrames; i++) { - print('FRAME:' + i.toString()); const localPaletteRequired = !framesUsingGlobalPalette.has(i); const palette = localPaletteRequired ? [] : globalPalette; const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height); @@ -347,15 +349,12 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { // Transparency optimization const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a)); - print(canBeTransparent, canBeTransparent.length > 0); if (canBeTransparent.length > 0) { // Select a color to mark as transparent const transparent = canBeTransparent[0]; - print(localPaletteRequired ? 'local' : 'global'); const transparentIndex = localPaletteRequired ? colorIndicesLookup[transparent] : globalIndicesLookup[transparent]; - print(transparent, transparentIndex); if (i > 0) { for (let k = 0; k < allFramesPixelColors[i].length; k++) { // If this pixel in this frame has the same color in previous frame @@ -383,7 +382,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { if (i > 0) { // add the frame that came before the current one // print('FRAME: ' + i.toString()); - print(previousFrame.frameOpts); + // print(previousFrame.frameOpts); // print(''); // print(''); gifWriter.addFrame( diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 3a355439a6..8a062d7b59 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -256,6 +256,7 @@ p5.prototype.saveGif = function(...args) { 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' ); + pixelDensity(1); const pd = this._pixelDensity; const width_pd = this.width * pd; const height_pd = this.height * pd; @@ -285,8 +286,6 @@ p5.prototype.saveGif = function(...args) { count++; } - print(frames[1]); - pImg.gifProperties = { displayIndex: 0, loopLimit: 0, // let it loop indefinitely From 739385cb6b1cde0952513b0a57c7187766e15e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Tue, 19 Jul 2022 19:01:46 +0200 Subject: [PATCH 17/61] using gifenc to successfully encode gifs! --- package-lock.json | 6 + package.json | 3 +- src/image/image.js | 246 ++++---------------------------- src/image/loading_displaying.js | 56 ++++---- 4 files changed, 63 insertions(+), 248 deletions(-) diff --git a/package-lock.json b/package-lock.json index ffff484561..72d7ac4ea0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6606,6 +6606,12 @@ } } }, + "gifenc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz", + "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw==", + "dev": true + }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", diff --git a/package.json b/package.json index 76acf7cb9b..f1d46de5ab 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,8 @@ "regenerator-runtime": "^0.13.3", "request": "^2.88.0", "simple-git": "^3.3.0", - "whatwg-fetch": "^2.0.4" + "whatwg-fetch": "^2.0.4", + "gifenc": "^1.0.3" }, "license": "LGPL-2.1", "main": "./lib/p5.min.js", diff --git a/src/image/image.js b/src/image/image.js index b0fa6074f5..a847231339 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -10,7 +10,8 @@ * for drawing images to the main display canvas. */ import p5 from '../core/main'; -import omggif from 'omggif'; +// import omggif from 'omggif'; +import { GIFEncoder, quantize, applyPalette } from 'gifenc'; /** * Creates a new p5.Image (the datatype for storing images). This provides a @@ -184,236 +185,41 @@ p5.prototype.saveCanvas = function() { }, mimeType); }; -p5.prototype.encodeAndDownloadGif = function(pImg, filename) { - const props = pImg.gifProperties; +p5.prototype.encodeAndDownloadGif = async function(pImg, filename) { + const frames = pImg.gifProperties.frames; - //convert loopLimit back into Netscape Block formatting - let loopLimit = props.loopLimit; - if (loopLimit === 1) { - loopLimit = null; - } else if (loopLimit === null) { - loopLimit = 0; - } - const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames); - - const allFramesPixelColors = []; - - // Used to determine the occurrence of unique palettes and the frames - // which use them - const paletteFreqsAndFrames = {}; - - // Pass 1: - //loop over frames and get the frequency of each palette - for (let i = 0; i < props.numFrames; i++) { - const paletteSet = new Set(); - const data = props.frames[i].image.data; - const dataLength = data.length; - // The color for each pixel in this frame ( for easier lookup later ) - const pixelColors = new Uint32Array(pImg.width * pImg.height); - for (let j = 0, k = 0; j < dataLength; j += 4, k++) { - const r = data[j + 0]; - const g = data[j + 1]; - const b = data[j + 2]; - const color = (r << 16) | (g << 8) | (b << 0); - paletteSet.add(color); - - // What color does this pixel have in this frame ? - pixelColors[k] = color; - } - - // A way to put use the entire palette as an object key - const paletteStr = [...paletteSet].sort().toString(); - if (paletteFreqsAndFrames[paletteStr] === undefined) { - paletteFreqsAndFrames[paletteStr] = { freq: 1, frames: [i] }; - } else { - paletteFreqsAndFrames[paletteStr].freq += 1; - paletteFreqsAndFrames[paletteStr].frames.push(i); - } - - allFramesPixelColors.push(pixelColors); - } - - let framesUsingGlobalPalette = []; - - // Now to build the global palette - // Sort all the unique palettes in descending order of their occurrence - const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function( - a, - b - ) { - return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; - }); - - // The initial global palette is the one with the most occurrence - const globalPalette = palettesSortedByFreq[0] - .split(',') - .map(a => parseInt(a)); + // Setup an encoder that we will write frames into + const gif = GIFEncoder(); - framesUsingGlobalPalette = framesUsingGlobalPalette.concat( - paletteFreqsAndFrames[globalPalette].frames - ); + // We use for 'of' to loop with async await + for (let i = 0; i < frames.length; i++) { + console.info('Processing frame ' + i.toString()); + // Get RGBA data from canvas + const data = frames[i].image.data; - const globalPaletteSet = new Set(globalPalette); + // Choose a pixel format: rgba4444, rgb444, rgb565 + const format = 'rgba4444'; - // Build a more complete global palette - // Iterate over the remaining palettes in the order of - // their occurrence and see if the colors in this palette which are - // not in the global palette can be added there, while keeping the length - // of the global palette <= 256 - for (let i = 1; i < palettesSortedByFreq.length; i++) { - const palette = palettesSortedByFreq[i].split(',').map(a => parseInt(a)); + // If necessary, quantize your colors to a reduced palette + const palette = quantize(data, 256, { format, clearAlpha: false }); - const difference = palette.filter(x => !globalPaletteSet.has(x)); - if (globalPalette.length + difference.length <= 256) { - print(globalPalette.length); - globalPalette.concat(difference); - difference.forEach(v => globalPaletteSet.add(v)); - // for (let j = 0; j < difference.length; j++) { - // // globalPalette.push(difference[j]); - // globalPaletteSet.add(difference[j]); - // } + // Apply palette to RGBA data to get an indexed bitmap + const index = applyPalette(data, palette, format); - // All frames using this palette now use the global palette - framesUsingGlobalPalette = framesUsingGlobalPalette.concat( - paletteFreqsAndFrames[palettesSortedByFreq[i]].frames - ); - } - } - - framesUsingGlobalPalette = new Set(framesUsingGlobalPalette); + // Write frame into GIF + gif.writeFrame(index, width, height, { palette, delay: frames[i].delay }); - // Build a lookup table of the index of each color in the global palette - // Maps a color to its index - const globalIndicesLookup = {}; - for (let i = 0; i < globalPalette.length; i++) { - if (!globalIndicesLookup[globalPalette[i]]) { - globalIndicesLookup[globalPalette[i]] = i; - } - } - - // force palette to be power of 2 - let powof2 = 1; - while (powof2 < globalPalette.length) { - powof2 <<= 1; - } - globalPalette.length = constrain(powof2, 2, 256); - - // global opts - const opts = { - loop: loopLimit, - palette: new Uint32Array(globalPalette) - }; - const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts); - let previousFrame = {}; - // Pass 2 - // Determine if the frame needs a local palette - // Also apply transparency optimization. This function will often blow up - // the size of a GIF if not for transparency. If a pixel in one frame has - // the same color in the previous frame, that pixel can be marked as - // transparent. We decide one particular color as transparent and make all - // transparent pixels take this color. This helps in later in compression. - for (let i = 0; i < props.numFrames; i++) { - const localPaletteRequired = !framesUsingGlobalPalette.has(i); - const palette = localPaletteRequired ? [] : globalPalette; - const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height); - - // Lookup table mapping color to its indices - const colorIndicesLookup = {}; - - // All the colors that cannot be marked transparent in this frame - const cannotBeTransparent = new Set(); - - for (let k = 0; k < allFramesPixelColors[i].length; k++) { - const color = allFramesPixelColors[i][k]; - if (localPaletteRequired) { - // local palette cannot be greater than 256 colors - if (colorIndicesLookup[color] === undefined && palette.length <= 255) { - colorIndicesLookup[color] = palette.length; - palette.push(color); - } - pixelPaletteIndex[k] = colorIndicesLookup[color]; - } else { - pixelPaletteIndex[k] = globalIndicesLookup[color]; - } - - if (i > 0) { - // If even one pixel of this color has changed in this frame - // from the previous frame, we cannot mark it as transparent - if (allFramesPixelColors[i - 1][k] !== color) { - cannotBeTransparent.add(color); - } - } - } - - const frameOpts = {}; - - // Transparency optimization - const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a)); - if (canBeTransparent.length > 0) { - // Select a color to mark as transparent - const transparent = canBeTransparent[0]; - const transparentIndex = localPaletteRequired - ? colorIndicesLookup[transparent] - : globalIndicesLookup[transparent]; - if (i > 0) { - for (let k = 0; k < allFramesPixelColors[i].length; k++) { - // If this pixel in this frame has the same color in previous frame - if (allFramesPixelColors[i - 1][k] === allFramesPixelColors[i][k]) { - pixelPaletteIndex[k] = transparentIndex; - } - } - - frameOpts.transparent = transparentIndex; - // If this frame has any transparency, do not dispose the previous frame - previousFrame.frameOpts.disposal = 1; - } - } - - frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting - if (localPaletteRequired) { - // force palette to be power of 2 - let powof2 = 1; - while (powof2 < palette.length) { - powof2 <<= 1; - } - palette.length = constrain(powof2, 2, 256); - frameOpts.palette = new Uint32Array(palette); - } - if (i > 0) { - // add the frame that came before the current one - // print('FRAME: ' + i.toString()); - // print(previousFrame.frameOpts); - // print(''); - // print(''); - gifWriter.addFrame( - 0, - 0, - pImg.width, - pImg.height, - previousFrame.pixelPaletteIndex, - previousFrame.frameOpts - ); - } - // previous frame object should now have details of this frame - previousFrame = { - pixelPaletteIndex, - frameOpts - }; + // Wait a tick so that we don't lock up browser + await new Promise(resolve => setTimeout(resolve, 0)); } - previousFrame.frameOpts.disposal = 1; - // add the last frame - gifWriter.addFrame( - 0, - 0, - pImg.width, - pImg.height, - previousFrame.pixelPaletteIndex, - previousFrame.frameOpts - ); + // Finalize stream + gif.finish(); + // Get a direct typed array view into the buffer to avoid copying it + const buffer = gif.bytesView(); const extension = 'gif'; - const blob = new Blob([buffer.slice(0, gifWriter.end())], { + const blob = new Blob([buffer], { type: 'image/gif' }); p5.prototype.downloadFile(blob, filename, extension); diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8a062d7b59..15061176a2 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -10,6 +10,7 @@ import Filters from './filters'; import canvas from '../core/helpers'; import * as constants from '../core/constants'; import omggif from 'omggif'; +import { GIFEncoder, quantize, applyPalette } from 'gifenc'; import '../core/friendly_errors/validate_params'; import '../core/friendly_errors/file_errors'; @@ -207,7 +208,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * @alt * animation of a circle moving smoothly diagonally */ -p5.prototype.saveGif = function(...args) { +p5.prototype.saveGif = async function(...args) { // process args let fileName; @@ -246,13 +247,12 @@ p5.prototype.saveGif = function(...args) { // initialize variables for the frames processing var count = nFramesDelay; - let frames = []; noLoop(); // we start on the frame set by the delay argument frameCount = nFramesDelay; - console.log( + console.info( 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' ); @@ -260,8 +260,11 @@ p5.prototype.saveGif = function(...args) { const pd = this._pixelDensity; const width_pd = this.width * pd; const height_pd = this.height * pd; - let pImg = new p5.Image(width_pd, height_pd); + const gif = GIFEncoder(); + const format = 'rgba4444'; + + let p = createP('Frames processed: '); while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -271,37 +274,36 @@ p5.prototype.saveGif = function(...args) { */ redraw(); - const prevFrameData = this.drawingContext.getImageData( - 0, - 0, - width_pd, - height_pd - ); + const data = this.drawingContext.getImageData(0, 0, width_pd, height_pd) + .data; - frames.push({ - image: prevFrameData, - delay: 20 - }); + const palette = quantize(data, 256, { format }); + + // Apply palette to RGBA data to get an indexed bitmap + const index = applyPalette(data, palette, format); + // Write frame into GIF + gif.writeFrame(index, width, height, { palette, delay: 20 }); + + await new Promise(resolve => setTimeout(resolve, 0)); + p.text = 'Frames processed: ' + (count - nFramesDelay).toString(); count++; } - pImg.gifProperties = { - displayIndex: 0, - loopLimit: 0, // let it loop indefinitely - loopCount: 0, - frames: frames, - numFrames: nFrames, - playing: true, - timeDisplayed: 0, - lastChangeTime: 0 - }; - console.info('Frames processed, encoding gif. This may take a while...'); - frames = []; + gif.finish(); + loop(); - p5.prototype.encodeAndDownloadGif(pImg, fileName); + + // Get a direct typed array view into the buffer to avoid copying it + const buffer = gif.bytesView(); + const extension = 'gif'; + const blob = new Blob([buffer], { + type: 'image/gif' + }); + + p5.prototype.downloadFile(blob, fileName, extension); }; /** From 07ba8a444f2b6223620b61ec8fcb1e9f76c61a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Tue, 19 Jul 2022 23:21:55 +0200 Subject: [PATCH 18/61] remove async --- lib/empty-example/index.html | 2 +- lib/empty-example/sketch.js | 440 ++++++++++++++++---------------- src/image/image.js | 4 +- src/image/loading_displaying.js | 9 +- 4 files changed, 234 insertions(+), 221 deletions(-) diff --git a/lib/empty-example/index.html b/lib/empty-example/index.html index 65f5f34363..788b9f3ee3 100644 --- a/lib/empty-example/index.html +++ b/lib/empty-example/index.html @@ -9,7 +9,7 @@ body { padding: 0; margin: 0; - background-color: black; + background-color: #1b1b1b; } diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 3f9232f212..fa859fe8e7 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,238 +1,250 @@ /* eslint-disable no-unused-vars */ -function setup() { - // put setup code here - createCanvas(200, 200); -} +// function setup() { +// // put setup code here +// createCanvas(500, 500); +// } -function draw() { - // put drawing code here - let hue = map(sin(frameCount / 100), -1, 1, 0, 100); - background(hue); - - line(width / 2, 0, width / 2, height); - line(0, height / 2, width, height / 2); - - fill(250); - circle( - 100 * sin(frameCount / 70) + width / 2, - 100 * sin(frameCount / 70) + height / 2, - 100 - ); -} +// function draw() { +// // put drawing code here +// let hue = map(sin(frameCount / 10), -1, 1, 127, 255); +// let hue_2 = map(sin(frameCount / 100) + 0.791, -1, 1, 127, 255); + +// strokeWeight(0); +// line(width / 2, 0, width / 2, height); +// line(0, height / 2, width, height / 2); + +// fill(40, 40, hue); +// rect(0, 0, width / 2, height / 2); + +// // fill(80, 80, hue); +// rect(width / 2, 0, width / 2, height / 2); + +// fill(0, 180, hue); +// rect(0, height / 2, width / 2, height / 2); + +// // fill(240, 240, 0); +// rect(width / 2, height / 2, width / 2, height / 2); + +// fill(250); +// stroke(250, 250, 20); +// strokeWeight(4); +// circle( +// 100 * sin(frameCount / 20) + width / 2, +// // 100 * sin(frameCount / 20) + height / 2, +// // width / 2, +// height / 2, +// 100 +// ); +// } -function mousePressed() { - if (mouseButton === RIGHT) { - saveGif('mySketch', 2, 3); - } -} +// function mousePressed() { +// if (mouseButton === RIGHT) { +// saveGif('mySketch', 10, 3); +// } +// } // / COMPLEX SKETCH -// let offset; -// let spacing; - -// // let font; -// // function preload() { -// // font = loadFont("../SF-Mono-Regular.otf"); -// // } +let offset; +let spacing; -// function setup() { -// randomSeed(125); - -// w = min(windowHeight, windowWidth); -// createCanvas(w, w); +function setup() { + // randomSeed(125); -// looping = false; -// saving = false; -// noLoop(); + w = min(windowHeight, windowWidth); + createCanvas(w, w); + print(w); + looping = false; + saving = false; + noLoop(); -// divisor = random(1.2, 3).toFixed(2); + divisor = random(1.2, 3).toFixed(2); -// frameWidth = w / divisor; -// offset = (-frameWidth + w) / 2; + frameWidth = w / divisor; + offset = (-frameWidth + w) / 2; -// gen_num_total_squares = int(random(2, 20)); -// spacing = frameWidth / gen_num_total_squares; + gen_num_total_squares = int(random(2, 20)); + spacing = frameWidth / gen_num_total_squares; -// initHue = random(0, 360); -// compColor = (initHue + 360 / random(1, 4)) % 360; + initHue = random(0, 360); + compColor = (initHue + 360 / random(1, 4)) % 360; -// gen_stroke_weight = random(-100, 100); -// gen_stroke_fade_speed = random(30, 150); -// gen_shift_small_squares = random(0, 10); + gen_stroke_weight = random(-100, 100); + gen_stroke_fade_speed = random(30, 150); + gen_shift_small_squares = random(0, 10); -// gen_offset_small_sq_i = random(3, 10); -// gen_offset_small_sq_j = random(3, 10); + gen_offset_small_sq_i = random(3, 10); + gen_offset_small_sq_j = random(3, 10); -// gen_rotation_speed = random(30, 250); + gen_rotation_speed = random(30, 250); -// gen_depth = random(5, 20); -// gen_offset_i = random(1, 10); -// gen_offset_j = random(1, 10); + gen_depth = random(5, 20); + gen_offset_i = random(1, 10); + gen_offset_j = random(1, 10); -// gen_transparency = random(20, 255); + gen_transparency = random(20, 255); -// background(24); -// } + background(24); +} -// function draw() { -// colorMode(HSB); -// background(initHue, 80, 20, gen_transparency); -// makeSquares(); -// // addHandle(); +function draw() { + colorMode(HSB); + background(initHue, 80, 20, gen_transparency); + makeSquares(); + // addHandle(); -// if (saving) save('grid' + frameCount + '.png'); -// } + if (saving) save('grid' + frameCount + '.png'); +} -// function makeSquares(depth = gen_depth) { -// colorMode(HSB); -// let count_i = 0; - -// for (let i = offset; i < w - offset; i += spacing) { -// let count_j = 0; -// count_i++; - -// if (count_i > gen_num_total_squares) break; - -// for (let j = offset; j < w - offset; j += spacing) { -// count_j++; - -// if (count_j > gen_num_total_squares) break; - -// for (let n = 0; n < depth; n++) { -// noFill(); - -// if (n === 0) { -// stroke(initHue, 100, 100); -// fill( -// initHue, -// 100, -// 100, -// map( -// sin( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 0.3 -// ) -// ); -// } else { -// stroke(compColor, map(n, 0, depth, 100, 0), 100); -// fill( -// compColor, -// 100, -// 100, -// map( -// cos( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 0.3 -// ) -// ); -// } - -// strokeWeight( -// map( -// sin( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 1.5 -// ) -// ); - -// push(); -// translate(i + spacing / 2, j + spacing / 2); - -// rotate( -// i * gen_offset_i + -// j * gen_offset_j + -// frameCount / (gen_rotation_speed / (n + 1)) -// ); - -// if (n % 2 !== 0) { -// translate( -// sin(frameCount / 50) * gen_shift_small_squares, -// cos(frameCount / 50) * gen_shift_small_squares -// ); -// rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); -// } - -// if (n > 0) -// rect( -// -spacing / (gen_offset_small_sq_i + n), -// -spacing / (gen_offset_small_sq_j + n), -// spacing / (n + 1), -// spacing / (n + 1) -// ); -// else rect(-spacing / 2, -spacing / 2, spacing, spacing); - -// pop(); -// } -// // strokeWeight(40); -// // point(i, j); -// } -// } -// } +function makeSquares(depth = gen_depth) { + colorMode(HSB); + let count_i = 0; + + for (let i = offset; i < w - offset; i += spacing) { + let count_j = 0; + count_i++; + + if (count_i > gen_num_total_squares) break; + + for (let j = offset; j < w - offset; j += spacing) { + count_j++; + + if (count_j > gen_num_total_squares) break; + + for (let n = 0; n < depth; n++) { + noFill(); + + if (n === 0) { + stroke(initHue, 100, 100); + fill( + initHue, + 100, + 100, + map( + sin( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 0.3 + ) + ); + } else { + stroke(compColor, map(n, 0, depth, 100, 0), 100); + fill( + compColor, + 100, + 100, + map( + cos( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 0.3 + ) + ); + } + + strokeWeight( + map( + sin( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 1.5 + ) + ); + + push(); + translate(i + spacing / 2, j + spacing / 2); + + rotate( + i * gen_offset_i + + j * gen_offset_j + + frameCount / (gen_rotation_speed / (n + 1)) + ); + + if (n % 2 !== 0) { + translate( + sin(frameCount / 50) * gen_shift_small_squares, + cos(frameCount / 50) * gen_shift_small_squares + ); + rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); + } + + if (n > 0) + rect( + -spacing / (gen_offset_small_sq_i + n), + -spacing / (gen_offset_small_sq_j + n), + spacing / (n + 1), + spacing / (n + 1) + ); + else rect(-spacing / 2, -spacing / 2, spacing, spacing); + + pop(); + } + // strokeWeight(40); + // point(i, j); + } + } +} -// function addHandle() { -// fill(40); -// noStroke(); -// textAlign(RIGHT, BOTTOM); -// textFont(font); -// textSize(20); -// text('@jesi_rgb', w - 30, w - 30); -// } +function addHandle() { + fill(40); + noStroke(); + textAlign(RIGHT, BOTTOM); + textFont(font); + textSize(20); + text('@jesi_rgb', w - 30, w - 30); +} -// function mousePressed() { -// if (mouseButton === LEFT) { -// if (looping) { -// noLoop(); -// looping = false; -// } else { -// loop(); -// looping = true; -// } -// } -// } +function mousePressed() { + if (mouseButton === LEFT) { + if (looping) { + noLoop(); + looping = false; + } else { + loop(); + looping = true; + } + } +} -// function keyPressed() { -// console.log(key); -// switch (key) { -// // pressing the 's' key -// case 's': -// saveGif('mySketch', 2); -// break; - -// // pressing the '0' key -// case '0': -// frameCount = 0; -// loop(); -// noLoop(); -// break; - -// // pressing the ← key -// case 'ArrowLeft': -// frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); -// noLoop(); -// console.log(frameCount); -// break; - -// // pressing the → key -// case 'ArrowRights': -// frameCount += 1; -// noLoop(); -// console.log(frameCount); -// break; - -// default: -// break; -// } -// } +function keyPressed() { + console.log(key); + switch (key) { + // pressing the 's' key + case 's': + saveGif('mySketch', 3); + break; + + // pressing the '0' key + case '0': + frameCount = 0; + loop(); + noLoop(); + break; + + // pressing the ← key + case 'ArrowLeft': + frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); + noLoop(); + console.log(frameCount); + break; + + // pressing the → key + case 'ArrowRights': + frameCount += 1; + noLoop(); + console.log(frameCount); + break; + + default: + break; + } +} diff --git a/src/image/image.js b/src/image/image.js index a847231339..878f377119 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -185,7 +185,7 @@ p5.prototype.saveCanvas = function() { }, mimeType); }; -p5.prototype.encodeAndDownloadGif = async function(pImg, filename) { +p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const frames = pImg.gifProperties.frames; // Setup an encoder that we will write frames into @@ -210,7 +210,7 @@ p5.prototype.encodeAndDownloadGif = async function(pImg, filename) { gif.writeFrame(index, width, height, { palette, delay: frames[i].delay }); // Wait a tick so that we don't lock up browser - await new Promise(resolve => setTimeout(resolve, 0)); + // await new Promise(resolve => setTimeout(resolve, 0)); } // Finalize stream diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 15061176a2..101a1b2d2e 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -208,7 +208,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * @alt * animation of a circle moving smoothly diagonally */ -p5.prototype.saveGif = async function(...args) { +p5.prototype.saveGif = function(...args) { // process args let fileName; @@ -264,7 +264,8 @@ p5.prototype.saveGif = async function(...args) { const gif = GIFEncoder(); const format = 'rgba4444'; - let p = createP('Frames processed: '); + // let p = createP('Frames processed: '); + while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -285,8 +286,8 @@ p5.prototype.saveGif = async function(...args) { // Write frame into GIF gif.writeFrame(index, width, height, { palette, delay: 20 }); - await new Promise(resolve => setTimeout(resolve, 0)); - p.text = 'Frames processed: ' + (count - nFramesDelay).toString(); + // await new Promise(resolve => setTimeout(resolve, 0)); + // p.text = 'Frames processed: ' + (count - nFramesDelay).toString(); count++; } From e13735566be0b1c48456110c6406ca109a3a92db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 20 Jul 2022 13:02:10 +0200 Subject: [PATCH 19/61] removing unnecesary code --- src/image/image.js | 41 --------------------------------- src/image/loading_displaying.js | 10 +++----- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index 878f377119..50cc364da5 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -11,7 +11,6 @@ */ import p5 from '../core/main'; // import omggif from 'omggif'; -import { GIFEncoder, quantize, applyPalette } from 'gifenc'; /** * Creates a new p5.Image (the datatype for storing images). This provides a @@ -185,46 +184,6 @@ p5.prototype.saveCanvas = function() { }, mimeType); }; -p5.prototype.encodeAndDownloadGif = function(pImg, filename) { - const frames = pImg.gifProperties.frames; - - // Setup an encoder that we will write frames into - const gif = GIFEncoder(); - - // We use for 'of' to loop with async await - for (let i = 0; i < frames.length; i++) { - console.info('Processing frame ' + i.toString()); - // Get RGBA data from canvas - const data = frames[i].image.data; - - // Choose a pixel format: rgba4444, rgb444, rgb565 - const format = 'rgba4444'; - - // If necessary, quantize your colors to a reduced palette - const palette = quantize(data, 256, { format, clearAlpha: false }); - - // Apply palette to RGBA data to get an indexed bitmap - const index = applyPalette(data, palette, format); - - // Write frame into GIF - gif.writeFrame(index, width, height, { palette, delay: frames[i].delay }); - - // Wait a tick so that we don't lock up browser - // await new Promise(resolve => setTimeout(resolve, 0)); - } - - // Finalize stream - gif.finish(); - - // Get a direct typed array view into the buffer to avoid copying it - const buffer = gif.bytesView(); - const extension = 'gif'; - const blob = new Blob([buffer], { - type: 'image/gif' - }); - p5.prototype.downloadFile(blob, filename, extension); -}; - /** * Capture a sequence of frames that can be used to create a movie. * Accepts a callback. For example, you may wish to send the frames diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 101a1b2d2e..433e2a5769 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -264,11 +264,9 @@ p5.prototype.saveGif = function(...args) { const gif = GIFEncoder(); const format = 'rgba4444'; - // let p = createP('Frames processed: '); - while (count < nFrames + nFramesDelay) { - /* - we draw the next frame. this is important, since + /* + we draw the next frame. this is important, since busy sketches or low end devices might take longer to render some frames. So we just wait for the frame to be drawn and immediately save it to a buffer and continue @@ -286,14 +284,12 @@ p5.prototype.saveGif = function(...args) { // Write frame into GIF gif.writeFrame(index, width, height, { palette, delay: 20 }); - // await new Promise(resolve => setTimeout(resolve, 0)); - // p.text = 'Frames processed: ' + (count - nFramesDelay).toString(); count++; } console.info('Frames processed, encoding gif. This may take a while...'); - gif.finish(); + // gif.finish(); loop(); From 6632a24e688911bd5c15fbb8cf97074b723d683a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 20 Jul 2022 13:10:09 +0200 Subject: [PATCH 20/61] undo weird change --- src/image/image.js | 10 ++++------ src/image/loading_displaying.js | 11 +++++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index b0fa6074f5..6c08eb0997 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -266,12 +266,10 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const difference = palette.filter(x => !globalPaletteSet.has(x)); if (globalPalette.length + difference.length <= 256) { print(globalPalette.length); - globalPalette.concat(difference); - difference.forEach(v => globalPaletteSet.add(v)); - // for (let j = 0; j < difference.length; j++) { - // // globalPalette.push(difference[j]); - // globalPaletteSet.add(difference[j]); - // } + for (let j = 0; j < difference.length; j++) { + globalPalette.push(difference[j]); + globalPaletteSet.add(difference[j]); + } // All frames using this palette now use the global palette framesUsingGlobalPalette = framesUsingGlobalPalette.concat( diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8a062d7b59..577740cdb2 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -248,15 +248,14 @@ p5.prototype.saveGif = function(...args) { var count = nFramesDelay; let frames = []; - noLoop(); + this.noLoop(); // we start on the frame set by the delay argument - frameCount = nFramesDelay; - + this.frameCount = nFramesDelay; console.log( 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' ); - pixelDensity(1); + this.pixelDensity(1); const pd = this._pixelDensity; const width_pd = this.width * pd; const height_pd = this.height * pd; @@ -269,7 +268,7 @@ p5.prototype.saveGif = function(...args) { to render some frames. So we just wait for the frame to be drawn and immediately save it to a buffer and continue */ - redraw(); + this.redraw(); const prevFrameData = this.drawingContext.getImageData( 0, @@ -300,7 +299,7 @@ p5.prototype.saveGif = function(...args) { console.info('Frames processed, encoding gif. This may take a while...'); frames = []; - loop(); + this.loop(); p5.prototype.encodeAndDownloadGif(pImg, fileName); }; From 10dc92627d614285716ddf2f24f12c1f972b0e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 20 Jul 2022 13:10:42 +0200 Subject: [PATCH 21/61] removing and cleaning --- src/image/image.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/image/image.js b/src/image/image.js index 6c08eb0997..270b80a1f5 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -265,7 +265,6 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const difference = palette.filter(x => !globalPaletteSet.has(x)); if (globalPalette.length + difference.length <= 256) { - print(globalPalette.length); for (let j = 0; j < difference.length; j++) { globalPalette.push(difference[j]); globalPaletteSet.add(difference[j]); @@ -378,11 +377,6 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { frameOpts.palette = new Uint32Array(palette); } if (i > 0) { - // add the frame that came before the current one - // print('FRAME: ' + i.toString()); - // print(previousFrame.frameOpts); - // print(''); - // print(''); gifWriter.addFrame( 0, 0, From b495c6798b629675535a7772d10561a6712f7a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 20 Jul 2022 13:55:57 +0200 Subject: [PATCH 22/61] using global palette --- src/image/loading_displaying.js | 36 +++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 433e2a5769..b550d8de05 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -258,12 +258,13 @@ p5.prototype.saveGif = function(...args) { pixelDensity(1); const pd = this._pixelDensity; + + // width and height based on (p)ixel (d)ensity const width_pd = this.width * pd; const height_pd = this.height * pd; - const gif = GIFEncoder(); - const format = 'rgba4444'; - + // We first take every frame that we are going to use for the animation + let frames = []; while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -276,20 +277,33 @@ p5.prototype.saveGif = function(...args) { const data = this.drawingContext.getImageData(0, 0, width_pd, height_pd) .data; - const palette = quantize(data, 256, { format }); + frames.push(data); + count++; + } + console.info('Frames processed, encoding gif. This may take a while...'); + // create the gif encoder and the colorspace format + const gif = GIFEncoder(); + const format = 'rgba4444'; + + // first generate an optimal palette for the whole animation + const palette = quantize(frames[0], 256, { format }); + for (let i = 0; i < frames.length; i++) { // Apply palette to RGBA data to get an indexed bitmap - const index = applyPalette(data, palette, format); + const index = applyPalette(frames[i], palette, format); - // Write frame into GIF - gif.writeFrame(index, width, height, { palette, delay: 20 }); + // Write frame into the encoder - count++; - } + //if it's the first frame, also add what will be the global palette + if (i === 0) { + gif.writeFrame(index, width_pd, height_pd, { palette, delay: 20 }); + } - console.info('Frames processed, encoding gif. This may take a while...'); + // all subsequent frames will just use the global palette + gif.writeFrame(index, width_pd, height_pd, { delay: 20 }); + } - // gif.finish(); + gif.finish(); loop(); From f46d0eb5b49ea2ca7628a81be55c3000968454db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 20 Jul 2022 16:42:45 +0200 Subject: [PATCH 23/61] made a different global palette generator --- lib/empty-example/sketch.js | 424 ++++++++++++++++++------------------ src/image/image.js | 48 ++-- 2 files changed, 244 insertions(+), 228 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 3f9232f212..5e98625eaa 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,238 +1,238 @@ /* eslint-disable no-unused-vars */ -function setup() { - // put setup code here - createCanvas(200, 200); -} +// function setup() { +// // put setup code here +// createCanvas(300, 300); +// } -function draw() { - // put drawing code here - let hue = map(sin(frameCount / 100), -1, 1, 0, 100); - background(hue); - - line(width / 2, 0, width / 2, height); - line(0, height / 2, width, height / 2); - - fill(250); - circle( - 100 * sin(frameCount / 70) + width / 2, - 100 * sin(frameCount / 70) + height / 2, - 100 - ); -} +// function draw() { +// // put drawing code here +// let hue = map(sin(frameCount / 100), -1, 1, 0, 100); +// background(hue, 30, 100); + +// line(width / 2, 0, width / 2, height); +// line(0, height / 2, width, height / 2); + +// fill(250, hue); +// circle( +// 100 * sin(frameCount / 70) + width / 2, +// 100 * sin(frameCount / 70) + height / 2, +// 100 +// ); +// } -function mousePressed() { - if (mouseButton === RIGHT) { - saveGif('mySketch', 2, 3); - } -} +// function mousePressed() { +// if (mouseButton === RIGHT) { +// saveGif('mySketch', 2, 3); +// } +// } // / COMPLEX SKETCH -// let offset; -// let spacing; +let offset; +let spacing; -// // let font; -// // function preload() { -// // font = loadFont("../SF-Mono-Regular.otf"); -// // } +// let font; +// function preload() { +// font = loadFont("../SF-Mono-Regular.otf"); +// } -// function setup() { -// randomSeed(125); +function setup() { + randomSeed(125); -// w = min(windowHeight, windowWidth); -// createCanvas(w, w); + w = min(windowHeight, windowWidth); + createCanvas(400, 400); -// looping = false; -// saving = false; -// noLoop(); + looping = false; + saving = false; + noLoop(); -// divisor = random(1.2, 3).toFixed(2); + divisor = random(1.2, 3).toFixed(2); -// frameWidth = w / divisor; -// offset = (-frameWidth + w) / 2; + frameWidth = w / divisor; + offset = (-frameWidth + w) / 2; -// gen_num_total_squares = int(random(2, 20)); -// spacing = frameWidth / gen_num_total_squares; + gen_num_total_squares = int(random(2, 20)); + spacing = frameWidth / gen_num_total_squares; -// initHue = random(0, 360); -// compColor = (initHue + 360 / random(1, 4)) % 360; + initHue = random(0, 360); + compColor = (initHue + 360 / random(1, 4)) % 360; -// gen_stroke_weight = random(-100, 100); -// gen_stroke_fade_speed = random(30, 150); -// gen_shift_small_squares = random(0, 10); + gen_stroke_weight = random(-100, 100); + gen_stroke_fade_speed = random(30, 150); + gen_shift_small_squares = random(0, 10); -// gen_offset_small_sq_i = random(3, 10); -// gen_offset_small_sq_j = random(3, 10); + gen_offset_small_sq_i = random(3, 10); + gen_offset_small_sq_j = random(3, 10); -// gen_rotation_speed = random(30, 250); + gen_rotation_speed = random(30, 250); -// gen_depth = random(5, 20); -// gen_offset_i = random(1, 10); -// gen_offset_j = random(1, 10); + gen_depth = random(5, 20); + gen_offset_i = random(1, 10); + gen_offset_j = random(1, 10); -// gen_transparency = random(20, 255); + gen_transparency = random(20, 255); -// background(24); -// } + background(24); +} -// function draw() { -// colorMode(HSB); -// background(initHue, 80, 20, gen_transparency); -// makeSquares(); -// // addHandle(); +function draw() { + colorMode(HSB); + background(initHue, 80, 20, gen_transparency); + makeSquares(); + // addHandle(); -// if (saving) save('grid' + frameCount + '.png'); -// } + if (saving) save('grid' + frameCount + '.png'); +} -// function makeSquares(depth = gen_depth) { -// colorMode(HSB); -// let count_i = 0; - -// for (let i = offset; i < w - offset; i += spacing) { -// let count_j = 0; -// count_i++; - -// if (count_i > gen_num_total_squares) break; - -// for (let j = offset; j < w - offset; j += spacing) { -// count_j++; - -// if (count_j > gen_num_total_squares) break; - -// for (let n = 0; n < depth; n++) { -// noFill(); - -// if (n === 0) { -// stroke(initHue, 100, 100); -// fill( -// initHue, -// 100, -// 100, -// map( -// sin( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 0.3 -// ) -// ); -// } else { -// stroke(compColor, map(n, 0, depth, 100, 0), 100); -// fill( -// compColor, -// 100, -// 100, -// map( -// cos( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 0.3 -// ) -// ); -// } - -// strokeWeight( -// map( -// sin( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 1.5 -// ) -// ); - -// push(); -// translate(i + spacing / 2, j + spacing / 2); - -// rotate( -// i * gen_offset_i + -// j * gen_offset_j + -// frameCount / (gen_rotation_speed / (n + 1)) -// ); - -// if (n % 2 !== 0) { -// translate( -// sin(frameCount / 50) * gen_shift_small_squares, -// cos(frameCount / 50) * gen_shift_small_squares -// ); -// rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); -// } - -// if (n > 0) -// rect( -// -spacing / (gen_offset_small_sq_i + n), -// -spacing / (gen_offset_small_sq_j + n), -// spacing / (n + 1), -// spacing / (n + 1) -// ); -// else rect(-spacing / 2, -spacing / 2, spacing, spacing); - -// pop(); -// } -// // strokeWeight(40); -// // point(i, j); -// } -// } -// } +function makeSquares(depth = gen_depth) { + colorMode(HSB); + let count_i = 0; + + for (let i = offset; i < w - offset; i += spacing) { + let count_j = 0; + count_i++; + + if (count_i > gen_num_total_squares) break; + + for (let j = offset; j < w - offset; j += spacing) { + count_j++; + + if (count_j > gen_num_total_squares) break; + + for (let n = 0; n < depth; n++) { + noFill(); + + if (n === 0) { + stroke(initHue, 100, 100); + fill( + initHue, + 100, + 100, + map( + sin( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 0.3 + ) + ); + } else { + stroke(compColor, map(n, 0, depth, 100, 0), 100); + fill( + compColor, + 100, + 100, + map( + cos( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 0.3 + ) + ); + } + + strokeWeight( + map( + sin( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 1.5 + ) + ); + + push(); + translate(i + spacing / 2, j + spacing / 2); + + rotate( + i * gen_offset_i + + j * gen_offset_j + + frameCount / (gen_rotation_speed / (n + 1)) + ); + + if (n % 2 !== 0) { + translate( + sin(frameCount / 50) * gen_shift_small_squares, + cos(frameCount / 50) * gen_shift_small_squares + ); + rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); + } + + if (n > 0) + rect( + -spacing / (gen_offset_small_sq_i + n), + -spacing / (gen_offset_small_sq_j + n), + spacing / (n + 1), + spacing / (n + 1) + ); + else rect(-spacing / 2, -spacing / 2, spacing, spacing); + + pop(); + } + // strokeWeight(40); + // point(i, j); + } + } +} -// function addHandle() { -// fill(40); -// noStroke(); -// textAlign(RIGHT, BOTTOM); -// textFont(font); -// textSize(20); -// text('@jesi_rgb', w - 30, w - 30); -// } +function addHandle() { + fill(40); + noStroke(); + textAlign(RIGHT, BOTTOM); + textFont(font); + textSize(20); + text('@jesi_rgb', w - 30, w - 30); +} -// function mousePressed() { -// if (mouseButton === LEFT) { -// if (looping) { -// noLoop(); -// looping = false; -// } else { -// loop(); -// looping = true; -// } -// } -// } +function mousePressed() { + if (mouseButton === LEFT) { + if (looping) { + noLoop(); + looping = false; + } else { + loop(); + looping = true; + } + } +} -// function keyPressed() { -// console.log(key); -// switch (key) { -// // pressing the 's' key -// case 's': -// saveGif('mySketch', 2); -// break; - -// // pressing the '0' key -// case '0': -// frameCount = 0; -// loop(); -// noLoop(); -// break; - -// // pressing the ← key -// case 'ArrowLeft': -// frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); -// noLoop(); -// console.log(frameCount); -// break; - -// // pressing the → key -// case 'ArrowRights': -// frameCount += 1; -// noLoop(); -// console.log(frameCount); -// break; - -// default: -// break; -// } -// } +function keyPressed() { + console.log(key); + switch (key) { + // pressing the 's' key + case 's': + saveGif('mySketch', 2); + break; + + // pressing the '0' key + case '0': + frameCount = 0; + loop(); + noLoop(); + break; + + // pressing the ← key + case 'ArrowLeft': + frameCount > 0 ? (frameCount -= 1) : (frameCount = 0); + noLoop(); + console.log(frameCount); + break; + + // pressing the → key + case 'ArrowRights': + frameCount += 1; + noLoop(); + console.log(frameCount); + break; + + default: + break; + } +} diff --git a/src/image/image.js b/src/image/image.js index 270b80a1f5..32086020e9 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -197,6 +197,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames); const allFramesPixelColors = []; + const allColorsFreq = {}; // Used to determine the occurrence of unique palettes and the frames // which use them @@ -219,6 +220,12 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { // What color does this pixel have in this frame ? pixelColors[k] = color; + + if (allColorsFreq[color] === undefined) { + allColorsFreq[color] = { count: 1 }; + } else { + allColorsFreq[color].count += 1; + } } // A way to put use the entire palette as an object key @@ -244,14 +251,18 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; }); - // The initial global palette is the one with the most occurrence - const globalPalette = palettesSortedByFreq[0] - .split(',') + const allColorsSortedByFreq = Object.keys(allColorsFreq).sort((a, b) => { + return allColorsFreq[b].count - allColorsFreq[a].count; + }); + + // Take the top 256 colors + const globalPalette = allColorsSortedByFreq + .slice(0, 256) .map(a => parseInt(a)); - framesUsingGlobalPalette = framesUsingGlobalPalette.concat( - paletteFreqsAndFrames[globalPalette].frames - ); + // framesUsingGlobalPalette = framesUsingGlobalPalette.concat( + // paletteFreqsAndFrames[globalPalette].frames + // ); const globalPaletteSet = new Set(globalPalette); @@ -310,7 +321,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { // transparent. We decide one particular color as transparent and make all // transparent pixels take this color. This helps in later in compression. for (let i = 0; i < props.numFrames; i++) { - const localPaletteRequired = !framesUsingGlobalPalette.has(i); + const localPaletteRequired = false; const palette = localPaletteRequired ? [] : globalPalette; const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height); @@ -324,7 +335,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const color = allFramesPixelColors[i][k]; if (localPaletteRequired) { // local palette cannot be greater than 256 colors - if (colorIndicesLookup[color] === undefined && palette.length <= 255) { + if (colorIndicesLookup[color] === undefined && palette.length < 256) { colorIndicesLookup[color] = palette.length; palette.push(color); } @@ -367,15 +378,20 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) { } frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting - if (localPaletteRequired) { - // force palette to be power of 2 - let powof2 = 1; - while (powof2 < palette.length) { - powof2 <<= 1; - } - palette.length = constrain(powof2, 2, 256); - frameOpts.palette = new Uint32Array(palette); + // write the global palette with the first frame. + // subsequent frames will also use the global palette + if (i === 0) { + frameOpts.palette = new Uint32Array(globalPalette); } + // if (localPaletteRequired) { + // // force palette to be power of 2 + // let powof2 = 1; + // while (powof2 < palette.length) { + // powof2 <<= 1; + // } + // palette.length = constrain(powof2, 2, 256); + // frameOpts.palette = new Uint32Array(palette); + // } if (i > 0) { gifWriter.addFrame( 0, From f775062ecf02e095bc330807c3fdcf92f7c80f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 20 Jul 2022 18:10:37 +0200 Subject: [PATCH 24/61] creating a global color palette --- src/image/loading_displaying.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index b550d8de05..0d6c633eed 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -287,16 +287,41 @@ p5.prototype.saveGif = function(...args) { const format = 'rgba4444'; // first generate an optimal palette for the whole animation - const palette = quantize(frames[0], 256, { format }); + + let colorFreq = {}; + for (let f of frames) { + let currPalette = quantize(f, 256, { format }); + for (let color of currPalette) { + color = color.toString(); + if (colorFreq[color] === undefined) { + colorFreq[color] = { count: 1 }; + } else { + colorFreq[color].count += 1; + } + } + } + + let colorsSortedByFreq = Object.keys(colorFreq) + .sort((a, b) => { + return colorFreq[b].count - colorFreq[a].count; + }) + .map(c => c.split(',').map(x => parseInt(x))); + + const globalPalette = colorsSortedByFreq.splice(0, 256); + print(globalPalette); + for (let i = 0; i < frames.length; i++) { // Apply palette to RGBA data to get an indexed bitmap - const index = applyPalette(frames[i], palette, format); + const index = applyPalette(frames[i], globalPalette, format); // Write frame into the encoder //if it's the first frame, also add what will be the global palette if (i === 0) { - gif.writeFrame(index, width_pd, height_pd, { palette, delay: 20 }); + gif.writeFrame(index, width_pd, height_pd, { + palette: globalPalette, + delay: 20 + }); } // all subsequent frames will just use the global palette From f9e4735d86056b3a203b920a2b72dcc6cbc2391c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 21 Jul 2022 12:57:32 +0200 Subject: [PATCH 25/61] fancier palette generation and manipulation --- src/image/loading_displaying.js | 124 +++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 27 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 0d6c633eed..bc96c83c71 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -208,7 +208,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * @alt * animation of a circle moving smoothly diagonally */ -p5.prototype.saveGif = function(...args) { +p5.prototype.saveGif = async function(...args) { // process args let fileName; @@ -286,46 +286,69 @@ p5.prototype.saveGif = function(...args) { const gif = GIFEncoder(); const format = 'rgba4444'; - // first generate an optimal palette for the whole animation + // calculate the global palette for this set of frames + const globalPalette = generateGlobalPalette(frames, format); - let colorFreq = {}; - for (let f of frames) { - let currPalette = quantize(f, 256, { format }); - for (let color of currPalette) { - color = color.toString(); - if (colorFreq[color] === undefined) { - colorFreq[color] = { count: 1 }; - } else { - colorFreq[color].count += 1; + // we are going to iterate the frames in pairs, n-1 and n + for (let i = 0; i < frames.length; i++) { + let currFramePixels = frames[i]; + let lastFramePixels = frames[i - 1]; + + //matching pixels between frames can be set to full transparency, + // kinda digging a "hole" into the frame to see the pixels that where behind it + // (which would be the exact same, so not noticeable changes) + // this helps make the file smaller + let matchingPixelsInFrames = []; + if (i > 0) { + for (let p = 0; p < currFramePixels.length; p += 4) { + let currPixel = [ + currFramePixels[p], + currFramePixels[p + 1], + currFramePixels[p + 2], + currFramePixels[p + 3] + ]; + let lastPixel = [ + lastFramePixels[p], + lastFramePixels[p + 1], + lastFramePixels[p + 2], + lastFramePixels[p + 3] + ]; + if (pixelEquals(currPixel, lastPixel)) { + matchingPixelsInFrames.push(parseInt(p / 4)); + } } } - } - let colorsSortedByFreq = Object.keys(colorFreq) - .sort((a, b) => { - return colorFreq[b].count - colorFreq[a].count; - }) - .map(c => c.split(',').map(x => parseInt(x))); - - const globalPalette = colorsSortedByFreq.splice(0, 256); - print(globalPalette); - - for (let i = 0; i < frames.length; i++) { + // we decide on one of this colors to be fully transparent + const transparentIndex = matchingPixelsInFrames[0]; // Apply palette to RGBA data to get an indexed bitmap - const index = applyPalette(frames[i], globalPalette, format); + const indexedFrame = applyPalette(frames[i], globalPalette, { format }); + + for (let mp = 0; mp < matchingPixelsInFrames.length; mp++) { + let samePixelIndex = matchingPixelsInFrames[mp]; + indexedFrame[samePixelIndex] = transparentIndex; + } // Write frame into the encoder - //if it's the first frame, also add what will be the global palette + // if it's the first frame, also add what will be the global palette if (i === 0) { - gif.writeFrame(index, width_pd, height_pd, { + gif.writeFrame(indexedFrame, width_pd, height_pd, { palette: globalPalette, - delay: 20 + delay: 20, + dispose: 1 }); } // all subsequent frames will just use the global palette - gif.writeFrame(index, width_pd, height_pd, { delay: 20 }); + gif.writeFrame(indexedFrame, width_pd, height_pd, { + delay: 20, + transparent: true, + transparentIndex: transparentIndex, + dispose: 1 + }); + + await new Promise(resolve => setTimeout(resolve, 0)); } gif.finish(); @@ -342,6 +365,53 @@ p5.prototype.saveGif = function(...args) { p5.prototype.downloadFile(blob, fileName, extension); }; +function generateGlobalPalette(frames, format) { + // for each frame, we'll keep track of the count of + // every unique color. that is: how many times does + // this particular color appear in every frame? + // Then we'll sort the colors and pick the top 256! + + // calculate the frequency table for the colors + let colorFreq = {}; + for (let f of frames) { + let currPalette = quantize(f, 256, { format }); + for (let color of currPalette) { + // colors are in the format [r, g, b, (a)], as in [255, 127, 45, 255] + // we'll convert the array to its string representation so it can be used as an index! + color = color.toString(); + if (colorFreq[color] === undefined) { + colorFreq[color] = { count: 1 }; + } else { + colorFreq[color].count += 1; + } + } + } + + // at this point colorFreq is a dict with {color: count}, + // telling us how many times each color appears in the whole animation + + // we process it undoing the string operation coverting that into + // an array of strings (['255', '127', '45', '255']) and then we convert + // that again to an array of integers + let colorsSortedByFreq = Object.keys(colorFreq) + .sort((a, b) => { + return colorFreq[b].count - colorFreq[a].count; + }) + .map(c => c.split(',').map(x => parseInt(x))); + + // now we simply extract the top 256 colors! + return colorsSortedByFreq.splice(0, 256); +} + +function pixelEquals(a, b) { + return ( + Array.isArray(a) && + Array.isArray(b) && + a.length === b.length && + a.every((val, index) => val === b[index]) + ); +} + /** * Helper function for loading GIF-based images */ From 01f393939348718c82d91f7a5276078027c600ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 21 Jul 2022 13:45:10 +0200 Subject: [PATCH 26/61] debuggnig and restructuring --- lib/empty-example/sketch.js | 2 +- src/image/loading_displaying.js | 62 ++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index fa859fe8e7..d85d3b4d95 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -220,7 +220,7 @@ function keyPressed() { switch (key) { // pressing the 's' key case 's': - saveGif('mySketch', 3); + saveGif('mySketch', 1); break; // pressing the '0' key diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index bc96c83c71..516bea9857 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -291,15 +291,24 @@ p5.prototype.saveGif = async function(...args) { // we are going to iterate the frames in pairs, n-1 and n for (let i = 0; i < frames.length; i++) { - let currFramePixels = frames[i]; - let lastFramePixels = frames[i - 1]; + if (i === 0) { + const indexedFrame = applyPalette(frames[i], globalPalette, { format }); + gif.writeFrame(indexedFrame, width_pd, height_pd, { + palette: globalPalette, + delay: 20, + dispose: 1 + }); + continue; + } - //matching pixels between frames can be set to full transparency, + // matching pixels between frames can be set to full transparency, // kinda digging a "hole" into the frame to see the pixels that where behind it // (which would be the exact same, so not noticeable changes) // this helps make the file smaller - let matchingPixelsInFrames = []; if (i > 0) { + let currFramePixels = frames[i]; + let lastFramePixels = frames[i - 1]; + let matchingPixelsInFrames = []; for (let p = 0; p < currFramePixels.length; p += 4) { let currPixel = [ currFramePixels[p], @@ -317,37 +326,34 @@ p5.prototype.saveGif = async function(...args) { matchingPixelsInFrames.push(parseInt(p / 4)); } } - } - - // we decide on one of this colors to be fully transparent - const transparentIndex = matchingPixelsInFrames[0]; - // Apply palette to RGBA data to get an indexed bitmap - const indexedFrame = applyPalette(frames[i], globalPalette, { format }); - - for (let mp = 0; mp < matchingPixelsInFrames.length; mp++) { - let samePixelIndex = matchingPixelsInFrames[mp]; - indexedFrame[samePixelIndex] = transparentIndex; - } - - // Write frame into the encoder + // we decide on one of this colors to be fully transparent + const transparentIndex = matchingPixelsInFrames[0]; + // Apply palette to RGBA data to get an indexed bitmap + const indexedFrame = applyPalette(frames[i], globalPalette, { format }); + + for (let mp = 0; mp < matchingPixelsInFrames.length; mp++) { + let samePixelIndex = matchingPixelsInFrames[mp]; + // here, we overwrite whatever color this pixel was assigned to + // with the color that we decided we are going to use as transparent. + // down in writeFrame we are going to tell the encoder that whenever + // it runs into "transparentIndex", just dig a hole there allowing to + // see through what was in the frame before it. + indexedFrame[samePixelIndex] = transparentIndex; + } + // Write frame into the encoder + // if it's the first frame, also add what will be the global palette - // if it's the first frame, also add what will be the global palette - if (i === 0) { + // all subsequent frames will just use the global palette gif.writeFrame(indexedFrame, width_pd, height_pd, { - palette: globalPalette, delay: 20, + transparent: true, + transparentIndex: transparentIndex, dispose: 1 }); } - // all subsequent frames will just use the global palette - gif.writeFrame(indexedFrame, width_pd, height_pd, { - delay: 20, - transparent: true, - transparentIndex: transparentIndex, - dispose: 1 - }); - + // this just makes the process asynchronous, preventing + // that the encoding locks up the browser await new Promise(resolve => setTimeout(resolve, 0)); } From 0326f85fbd01df4a7e5760fff180a36a75e0234d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 22 Jul 2022 19:08:07 +0200 Subject: [PATCH 27/61] minor changes --- lib/empty-example/sketch.js | 467 +++++++++++++++++--------------- src/image/loading_displaying.js | 5 +- 2 files changed, 245 insertions(+), 227 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index d85d3b4d95..726ecfc430 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,250 +1,267 @@ /* eslint-disable no-unused-vars */ -// function setup() { -// // put setup code here -// createCanvas(500, 500); -// } - -// function draw() { -// // put drawing code here -// let hue = map(sin(frameCount / 10), -1, 1, 127, 255); -// let hue_2 = map(sin(frameCount / 100) + 0.791, -1, 1, 127, 255); - -// strokeWeight(0); -// line(width / 2, 0, width / 2, height); -// line(0, height / 2, width, height / 2); - -// fill(40, 40, hue); -// rect(0, 0, width / 2, height / 2); - -// // fill(80, 80, hue); -// rect(width / 2, 0, width / 2, height / 2); - -// fill(0, 180, hue); -// rect(0, height / 2, width / 2, height / 2); - -// // fill(240, 240, 0); -// rect(width / 2, height / 2, width / 2, height / 2); - -// fill(250); -// stroke(250, 250, 20); -// strokeWeight(4); -// circle( -// 100 * sin(frameCount / 20) + width / 2, -// // 100 * sin(frameCount / 20) + height / 2, -// // width / 2, -// height / 2, -// 100 -// ); -// } +let saving = false; +function setup() { + // put setup code here + createCanvas(500, 500); +} -// function mousePressed() { -// if (mouseButton === RIGHT) { -// saveGif('mySketch', 10, 3); -// } -// } +function draw() { + // put drawing code here + let hue = map(sin(frameCount), -1, 1, 127, 255); + let hue_2 = map(sin(frameCount / 100) + 0.791, -1, 1, 127, 255); + + strokeWeight(0); + line(width / 2, 0, width / 2, height); + line(0, height / 2, width, height / 2); + + fill(250, 250, 20); + rect(0, 0, width / 2, height / 2); + + // fill(80, 80, hue); + rect(width / 2, 0, width / 2, height / 2); + + fill(20, 250, 250); + rect(0, height / 2, width / 2, height / 2); + + // fill(240, 240, 0); + rect(width / 2, height / 2, width / 2, height / 2); + + fill(30); + stroke(20, 250, 20); + strokeWeight(4); + circle( + 100 * sin(frameCount / 20) + width / 2, + // 100 * sin(frameCount / 20) + height / 2, + // width / 2, + height / 2, + 100 + ); + + if (saving) { + save('frame' + frameCount.toString()); + } +} -// / COMPLEX SKETCH -let offset; -let spacing; +function mousePressed() { + if (mouseButton === RIGHT) { + saveGif('mySketch', 1, 3); + } +} -function setup() { - // randomSeed(125); +function keyPressed() { + switch (key) { + case 's': + frameRate(3); + frameCount = 0; + saving = !saving; - w = min(windowHeight, windowWidth); - createCanvas(w, w); - print(w); - looping = false; - saving = false; - noLoop(); + if (!saving) frameRate(60); + break; + } +} - divisor = random(1.2, 3).toFixed(2); +// / COMPLEX SKETCH +// let offset; +// let spacing; - frameWidth = w / divisor; - offset = (-frameWidth + w) / 2; +// function setup() { +// randomSeed(1312); - gen_num_total_squares = int(random(2, 20)); - spacing = frameWidth / gen_num_total_squares; +// w = min(windowHeight, windowWidth); +// createCanvas(w, w); +// print(w); +// looping = false; +// saving = false; +// noLoop(); - initHue = random(0, 360); - compColor = (initHue + 360 / random(1, 4)) % 360; +// divisor = random(1.2, 3).toFixed(2); - gen_stroke_weight = random(-100, 100); - gen_stroke_fade_speed = random(30, 150); - gen_shift_small_squares = random(0, 10); +// frameWidth = w / divisor; +// offset = (-frameWidth + w) / 2; - gen_offset_small_sq_i = random(3, 10); - gen_offset_small_sq_j = random(3, 10); +// gen_num_total_squares = int(random(2, 20)); +// spacing = frameWidth / gen_num_total_squares; - gen_rotation_speed = random(30, 250); +// initHue = random(0, 360); +// compColor = (initHue + 360 / random(1, 4)) % 360; - gen_depth = random(5, 20); - gen_offset_i = random(1, 10); - gen_offset_j = random(1, 10); +// gen_stroke_weight = random(-100, 100); +// gen_stroke_fade_speed = random(30, 150); +// gen_shift_small_squares = random(0, 10); - gen_transparency = random(20, 255); +// gen_offset_small_sq_i = random(3, 10); +// gen_offset_small_sq_j = random(3, 10); - background(24); -} +// gen_rotation_speed = random(30, 250); -function draw() { - colorMode(HSB); - background(initHue, 80, 20, gen_transparency); - makeSquares(); - // addHandle(); +// gen_depth = random(5, 20); +// gen_offset_i = random(1, 10); +// gen_offset_j = random(1, 10); - if (saving) save('grid' + frameCount + '.png'); -} +// gen_transparency = random(20, 255); -function makeSquares(depth = gen_depth) { - colorMode(HSB); - let count_i = 0; - - for (let i = offset; i < w - offset; i += spacing) { - let count_j = 0; - count_i++; - - if (count_i > gen_num_total_squares) break; - - for (let j = offset; j < w - offset; j += spacing) { - count_j++; - - if (count_j > gen_num_total_squares) break; - - for (let n = 0; n < depth; n++) { - noFill(); - - if (n === 0) { - stroke(initHue, 100, 100); - fill( - initHue, - 100, - 100, - map( - sin( - gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed - ), - -1, - 1, - 0, - 0.3 - ) - ); - } else { - stroke(compColor, map(n, 0, depth, 100, 0), 100); - fill( - compColor, - 100, - 100, - map( - cos( - gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed - ), - -1, - 1, - 0, - 0.3 - ) - ); - } - - strokeWeight( - map( - sin( - gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed - ), - -1, - 1, - 0, - 1.5 - ) - ); - - push(); - translate(i + spacing / 2, j + spacing / 2); - - rotate( - i * gen_offset_i + - j * gen_offset_j + - frameCount / (gen_rotation_speed / (n + 1)) - ); - - if (n % 2 !== 0) { - translate( - sin(frameCount / 50) * gen_shift_small_squares, - cos(frameCount / 50) * gen_shift_small_squares - ); - rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); - } - - if (n > 0) - rect( - -spacing / (gen_offset_small_sq_i + n), - -spacing / (gen_offset_small_sq_j + n), - spacing / (n + 1), - spacing / (n + 1) - ); - else rect(-spacing / 2, -spacing / 2, spacing, spacing); - - pop(); - } - // strokeWeight(40); - // point(i, j); - } - } -} - -function addHandle() { - fill(40); - noStroke(); - textAlign(RIGHT, BOTTOM); - textFont(font); - textSize(20); - text('@jesi_rgb', w - 30, w - 30); -} +// background(24); +// } -function mousePressed() { - if (mouseButton === LEFT) { - if (looping) { - noLoop(); - looping = false; - } else { - loop(); - looping = true; - } - } -} +// function draw() { +// colorMode(HSB); +// background(initHue, 80, 20, gen_transparency); +// makeSquares(); +// // addHandle(); -function keyPressed() { - console.log(key); - switch (key) { - // pressing the 's' key - case 's': - saveGif('mySketch', 1); - break; +// if (saving) save('grid' + frameCount + '.png'); +// } - // pressing the '0' key - case '0': - frameCount = 0; - loop(); - noLoop(); - break; +// function makeSquares(depth = gen_depth) { +// colorMode(HSB); +// let count_i = 0; + +// for (let i = offset; i < w - offset; i += spacing) { +// let count_j = 0; +// count_i++; + +// if (count_i > gen_num_total_squares) break; + +// for (let j = offset; j < w - offset; j += spacing) { +// count_j++; + +// if (count_j > gen_num_total_squares) break; + +// for (let n = 0; n < depth; n++) { +// noFill(); + +// if (n === 0) { +// stroke(initHue, 100, 100); +// fill( +// initHue, +// 100, +// 100, +// map( +// sin( +// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed +// ), +// -1, +// 1, +// 0, +// 0.3 +// ) +// ); +// } else { +// stroke(compColor, map(n, 0, depth, 100, 0), 100); +// fill( +// compColor, +// 100, +// 100, +// map( +// cos( +// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed +// ), +// -1, +// 1, +// 0, +// 0.3 +// ) +// ); +// } + +// strokeWeight( +// map( +// sin( +// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed +// ), +// -1, +// 1, +// 0, +// 1.5 +// ) +// ); + +// push(); +// translate(i + spacing / 2, j + spacing / 2); + +// rotate( +// i * gen_offset_i + +// j * gen_offset_j + +// frameCount / (gen_rotation_speed / (n + 1)) +// ); + +// if (n % 2 !== 0) { +// translate( +// sin(frameCount / 50) * gen_shift_small_squares, +// cos(frameCount / 50) * gen_shift_small_squares +// ); +// rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); +// } + +// if (n > 0) +// rect( +// -spacing / (gen_offset_small_sq_i + n), +// -spacing / (gen_offset_small_sq_j + n), +// spacing / (n + 1), +// spacing / (n + 1) +// ); +// else rect(-spacing / 2, -spacing / 2, spacing, spacing); + +// pop(); +// } +// // strokeWeight(40); +// // point(i, j); +// } +// } +// } - // pressing the ← key - case 'ArrowLeft': - frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); - noLoop(); - console.log(frameCount); - break; +// function addHandle() { +// fill(40); +// noStroke(); +// textAlign(RIGHT, BOTTOM); +// textFont(font); +// textSize(20); +// text('@jesi_rgb', w - 30, w - 30); +// } - // pressing the → key - case 'ArrowRights': - frameCount += 1; - noLoop(); - console.log(frameCount); - break; +// function mousePressed() { +// if (mouseButton === LEFT) { +// if (looping) { +// noLoop(); +// looping = false; +// } else { +// loop(); +// looping = true; +// } +// } +// } - default: - break; - } -} +// function keyPressed() { +// console.log(key); +// switch (key) { +// // pressing the 's' key +// case 's': +// saveGif('mySketch', 1); +// break; + +// // pressing the '0' key +// case '0': +// frameCount = 0; +// loop(); +// noLoop(); +// break; + +// // pressing the ← key +// case 'ArrowLeft': +// frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); +// noLoop(); +// console.log(frameCount); +// break; + +// // pressing the → key +// case 'ArrowRights': +// frameCount += 1; +// noLoop(); +// console.log(frameCount); +// break; + +// default: +// break; +// } +// } diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 516bea9857..0265c23741 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -284,7 +284,7 @@ p5.prototype.saveGif = async function(...args) { // create the gif encoder and the colorspace format const gif = GIFEncoder(); - const format = 'rgba4444'; + const format = 'rgb444'; // calculate the global palette for this set of frames const globalPalette = generateGlobalPalette(frames, format); @@ -351,6 +351,7 @@ p5.prototype.saveGif = async function(...args) { dispose: 1 }); } + print('Frame: ' + i.toString()); // this just makes the process asynchronous, preventing // that the encoding locks up the browser @@ -380,7 +381,7 @@ function generateGlobalPalette(frames, format) { // calculate the frequency table for the colors let colorFreq = {}; for (let f of frames) { - let currPalette = quantize(f, 256, { format }); + let currPalette = quantize(f, 1024, { format }); for (let color of currPalette) { // colors are in the format [r, g, b, (a)], as in [255, 127, 45, 255] // we'll convert the array to its string representation so it can be used as an index! From 7256b1cd9d0aa655e5cb03b1723bf5dd2ca1cef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sat, 23 Jul 2022 09:32:01 +0200 Subject: [PATCH 28/61] solve bug with transparentIndex --- lib/empty-example/sketch.js | 474 ++++++++++++++++---------------- src/image/loading_displaying.js | 6 +- 2 files changed, 241 insertions(+), 239 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 726ecfc430..4dbb7d70c3 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,267 +1,267 @@ /* eslint-disable no-unused-vars */ -let saving = false; -function setup() { - // put setup code here - createCanvas(500, 500); -} +// let saving = false; +// function setup() { +// // put setup code here +// createCanvas(500, 500); +// } -function draw() { - // put drawing code here - let hue = map(sin(frameCount), -1, 1, 127, 255); - let hue_2 = map(sin(frameCount / 100) + 0.791, -1, 1, 127, 255); - - strokeWeight(0); - line(width / 2, 0, width / 2, height); - line(0, height / 2, width, height / 2); - - fill(250, 250, 20); - rect(0, 0, width / 2, height / 2); - - // fill(80, 80, hue); - rect(width / 2, 0, width / 2, height / 2); - - fill(20, 250, 250); - rect(0, height / 2, width / 2, height / 2); - - // fill(240, 240, 0); - rect(width / 2, height / 2, width / 2, height / 2); - - fill(30); - stroke(20, 250, 20); - strokeWeight(4); - circle( - 100 * sin(frameCount / 20) + width / 2, - // 100 * sin(frameCount / 20) + height / 2, - // width / 2, - height / 2, - 100 - ); - - if (saving) { - save('frame' + frameCount.toString()); - } -} +// function draw() { +// // put drawing code here +// let hue = map(sin(frameCount), -1, 1, 127, 255); +// let hue_2 = map(sin(frameCount / 100) + 0.791, -1, 1, 127, 255); + +// strokeWeight(0); +// line(width / 2, 0, width / 2, height); +// line(0, height / 2, width, height / 2); + +// fill(250, 250, 20); +// rect(0, 0, width / 2, height / 2); + +// // fill(80, 80, hue); +// rect(width / 2, 0, width / 2, height / 2); + +// fill(20, 250, 250); +// rect(0, height / 2, width / 2, height / 2); + +// // fill(240, 240, 0); +// rect(width / 2, height / 2, width / 2, height / 2); + +// fill(30); +// stroke(20, 250, 20); +// strokeWeight(4); +// circle( +// 100 * sin(frameCount / 20) + width / 2, +// // 100 * sin(frameCount / 20) + height / 2, +// // width / 2, +// height / 2, +// 100 +// ); + +// if (saving) { +// save('frame' + frameCount.toString()); +// } +// } -function mousePressed() { - if (mouseButton === RIGHT) { - saveGif('mySketch', 1, 3); - } -} +// function mousePressed() { +// if (mouseButton === RIGHT) { +// saveGif('mySketch', 1, 3); +// } +// } -function keyPressed() { - switch (key) { - case 's': - frameRate(3); - frameCount = 0; - saving = !saving; +// function keyPressed() { +// switch (key) { +// case 's': +// frameRate(3); +// frameCount = 0; +// saving = !saving; - if (!saving) frameRate(60); - break; - } -} +// if (!saving) frameRate(60); +// break; +// } +// } // / COMPLEX SKETCH -// let offset; -// let spacing; +let offset; +let spacing; -// function setup() { -// randomSeed(1312); +function setup() { + // randomSeed(1312); -// w = min(windowHeight, windowWidth); -// createCanvas(w, w); -// print(w); -// looping = false; -// saving = false; -// noLoop(); + w = min(windowHeight, windowWidth); + createCanvas(w, w); + print(w); + looping = false; + saving = false; + noLoop(); -// divisor = random(1.2, 3).toFixed(2); + divisor = random(1.2, 3).toFixed(2); -// frameWidth = w / divisor; -// offset = (-frameWidth + w) / 2; + frameWidth = w / divisor; + offset = (-frameWidth + w) / 2; -// gen_num_total_squares = int(random(2, 20)); -// spacing = frameWidth / gen_num_total_squares; + gen_num_total_squares = int(random(2, 20)); + spacing = frameWidth / gen_num_total_squares; -// initHue = random(0, 360); -// compColor = (initHue + 360 / random(1, 4)) % 360; + initHue = random(0, 360); + compColor = (initHue + 360 / random(1, 4)) % 360; -// gen_stroke_weight = random(-100, 100); -// gen_stroke_fade_speed = random(30, 150); -// gen_shift_small_squares = random(0, 10); + gen_stroke_weight = random(-100, 100); + gen_stroke_fade_speed = random(30, 150); + gen_shift_small_squares = random(0, 10); -// gen_offset_small_sq_i = random(3, 10); -// gen_offset_small_sq_j = random(3, 10); + gen_offset_small_sq_i = random(3, 10); + gen_offset_small_sq_j = random(3, 10); -// gen_rotation_speed = random(30, 250); + gen_rotation_speed = random(30, 250); -// gen_depth = random(5, 20); -// gen_offset_i = random(1, 10); -// gen_offset_j = random(1, 10); + gen_depth = random(5, 20); + gen_offset_i = random(1, 10); + gen_offset_j = random(1, 10); -// gen_transparency = random(20, 255); + gen_transparency = random(20, 255); -// background(24); -// } + background(24); +} -// function draw() { -// colorMode(HSB); -// background(initHue, 80, 20, gen_transparency); -// makeSquares(); -// // addHandle(); +function draw() { + colorMode(HSB); + background(initHue, 80, 20, gen_transparency); + makeSquares(); + // addHandle(); -// if (saving) save('grid' + frameCount + '.png'); -// } + if (saving) save('grid' + frameCount + '.png'); +} -// function makeSquares(depth = gen_depth) { -// colorMode(HSB); -// let count_i = 0; - -// for (let i = offset; i < w - offset; i += spacing) { -// let count_j = 0; -// count_i++; - -// if (count_i > gen_num_total_squares) break; - -// for (let j = offset; j < w - offset; j += spacing) { -// count_j++; - -// if (count_j > gen_num_total_squares) break; - -// for (let n = 0; n < depth; n++) { -// noFill(); - -// if (n === 0) { -// stroke(initHue, 100, 100); -// fill( -// initHue, -// 100, -// 100, -// map( -// sin( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 0.3 -// ) -// ); -// } else { -// stroke(compColor, map(n, 0, depth, 100, 0), 100); -// fill( -// compColor, -// 100, -// 100, -// map( -// cos( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 0.3 -// ) -// ); -// } - -// strokeWeight( -// map( -// sin( -// gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed -// ), -// -1, -// 1, -// 0, -// 1.5 -// ) -// ); - -// push(); -// translate(i + spacing / 2, j + spacing / 2); - -// rotate( -// i * gen_offset_i + -// j * gen_offset_j + -// frameCount / (gen_rotation_speed / (n + 1)) -// ); - -// if (n % 2 !== 0) { -// translate( -// sin(frameCount / 50) * gen_shift_small_squares, -// cos(frameCount / 50) * gen_shift_small_squares -// ); -// rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); -// } - -// if (n > 0) -// rect( -// -spacing / (gen_offset_small_sq_i + n), -// -spacing / (gen_offset_small_sq_j + n), -// spacing / (n + 1), -// spacing / (n + 1) -// ); -// else rect(-spacing / 2, -spacing / 2, spacing, spacing); - -// pop(); -// } -// // strokeWeight(40); -// // point(i, j); -// } -// } -// } +function makeSquares(depth = gen_depth) { + colorMode(HSB); + let count_i = 0; + + for (let i = offset; i < w - offset; i += spacing) { + let count_j = 0; + count_i++; + + if (count_i > gen_num_total_squares) break; + + for (let j = offset; j < w - offset; j += spacing) { + count_j++; + + if (count_j > gen_num_total_squares) break; + + for (let n = 0; n < depth; n++) { + noFill(); + + if (n === 0) { + stroke(initHue, 100, 100); + fill( + initHue, + 100, + 100, + map( + sin( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 0.3 + ) + ); + } else { + stroke(compColor, map(n, 0, depth, 100, 0), 100); + fill( + compColor, + 100, + 100, + map( + cos( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 0.3 + ) + ); + } + + strokeWeight( + map( + sin( + gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed + ), + -1, + 1, + 0, + 1.5 + ) + ); + + push(); + translate(i + spacing / 2, j + spacing / 2); + + rotate( + i * gen_offset_i + + j * gen_offset_j + + frameCount / (gen_rotation_speed / (n + 1)) + ); + + if (n % 2 !== 0) { + translate( + sin(frameCount / 50) * gen_shift_small_squares, + cos(frameCount / 50) * gen_shift_small_squares + ); + rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); + } + + if (n > 0) + rect( + -spacing / (gen_offset_small_sq_i + n), + -spacing / (gen_offset_small_sq_j + n), + spacing / (n + 1), + spacing / (n + 1) + ); + else rect(-spacing / 2, -spacing / 2, spacing, spacing); + + pop(); + } + // strokeWeight(40); + // point(i, j); + } + } +} -// function addHandle() { -// fill(40); -// noStroke(); -// textAlign(RIGHT, BOTTOM); -// textFont(font); -// textSize(20); -// text('@jesi_rgb', w - 30, w - 30); -// } +function addHandle() { + fill(40); + noStroke(); + textAlign(RIGHT, BOTTOM); + textFont(font); + textSize(20); + text('@jesi_rgb', w - 30, w - 30); +} -// function mousePressed() { -// if (mouseButton === LEFT) { -// if (looping) { -// noLoop(); -// looping = false; -// } else { -// loop(); -// looping = true; -// } -// } -// } +function mousePressed() { + if (mouseButton === LEFT) { + if (looping) { + noLoop(); + looping = false; + } else { + loop(); + looping = true; + } + } +} -// function keyPressed() { -// console.log(key); -// switch (key) { -// // pressing the 's' key -// case 's': -// saveGif('mySketch', 1); -// break; +function keyPressed() { + console.log(key); + switch (key) { + // pressing the 's' key + case 's': + saveGif('mySketch', 1); + break; -// // pressing the '0' key -// case '0': -// frameCount = 0; -// loop(); -// noLoop(); -// break; + // pressing the '0' key + case '0': + frameCount = 0; + loop(); + noLoop(); + break; -// // pressing the ← key -// case 'ArrowLeft': -// frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); -// noLoop(); -// console.log(frameCount); -// break; + // pressing the ← key + case 'ArrowLeft': + frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); + noLoop(); + console.log(frameCount); + break; -// // pressing the → key -// case 'ArrowRights': -// frameCount += 1; -// noLoop(); -// console.log(frameCount); -// break; + // pressing the → key + case 'ArrowRights': + frameCount += 1; + noLoop(); + console.log(frameCount); + break; -// default: -// break; -// } -// } + default: + break; + } +} diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 0265c23741..4323c6e37a 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -327,9 +327,11 @@ p5.prototype.saveGif = async function(...args) { } } // we decide on one of this colors to be fully transparent - const transparentIndex = matchingPixelsInFrames[0]; + const transparentIndex = currFramePixels[matchingPixelsInFrames[0]]; // Apply palette to RGBA data to get an indexed bitmap - const indexedFrame = applyPalette(frames[i], globalPalette, { format }); + const indexedFrame = applyPalette(currFramePixels, globalPalette, { + format + }); for (let mp = 0; mp < matchingPixelsInFrames.length; mp++) { let samePixelIndex = matchingPixelsInFrames[mp]; From de52c273f3f3ae691d094c5e90538e59ded7fd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sat, 23 Jul 2022 10:55:21 +0200 Subject: [PATCH 29/61] push latest changes meeting --- lib/empty-example/sketch.js | 2 +- src/image/loading_displaying.js | 90 ++++++++++++++++----------------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 4dbb7d70c3..844b6b4506 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -237,7 +237,7 @@ function keyPressed() { switch (key) { // pressing the 's' key case 's': - saveGif('mySketch', 1); + saveGif('mySketch', 6); break; // pressing the '0' key diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 4323c6e37a..bb17df7930 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -279,6 +279,8 @@ p5.prototype.saveGif = async function(...args) { frames.push(data); count++; + + await new Promise(resolve => setTimeout(resolve, 0)); } console.info('Frames processed, encoding gif. This may take a while...'); @@ -305,54 +307,50 @@ p5.prototype.saveGif = async function(...args) { // kinda digging a "hole" into the frame to see the pixels that where behind it // (which would be the exact same, so not noticeable changes) // this helps make the file smaller - if (i > 0) { - let currFramePixels = frames[i]; - let lastFramePixels = frames[i - 1]; - let matchingPixelsInFrames = []; - for (let p = 0; p < currFramePixels.length; p += 4) { - let currPixel = [ - currFramePixels[p], - currFramePixels[p + 1], - currFramePixels[p + 2], - currFramePixels[p + 3] - ]; - let lastPixel = [ - lastFramePixels[p], - lastFramePixels[p + 1], - lastFramePixels[p + 2], - lastFramePixels[p + 3] - ]; - if (pixelEquals(currPixel, lastPixel)) { - matchingPixelsInFrames.push(parseInt(p / 4)); - } - } - // we decide on one of this colors to be fully transparent - const transparentIndex = currFramePixels[matchingPixelsInFrames[0]]; - // Apply palette to RGBA data to get an indexed bitmap - const indexedFrame = applyPalette(currFramePixels, globalPalette, { - format - }); - - for (let mp = 0; mp < matchingPixelsInFrames.length; mp++) { - let samePixelIndex = matchingPixelsInFrames[mp]; - // here, we overwrite whatever color this pixel was assigned to - // with the color that we decided we are going to use as transparent. - // down in writeFrame we are going to tell the encoder that whenever - // it runs into "transparentIndex", just dig a hole there allowing to - // see through what was in the frame before it. - indexedFrame[samePixelIndex] = transparentIndex; + let currFramePixels = frames[i]; + let lastFramePixels = frames[i - 1]; + let matchingPixelsInFrames = []; + for (let p = 0; p < currFramePixels.length; p += 4) { + let currPixel = [ + currFramePixels[p], + currFramePixels[p + 1], + currFramePixels[p + 2], + currFramePixels[p + 3] + ]; + let lastPixel = [ + lastFramePixels[p], + lastFramePixels[p + 1], + lastFramePixels[p + 2], + lastFramePixels[p + 3] + ]; + if (pixelEquals(currPixel, lastPixel)) { + matchingPixelsInFrames.push(parseInt(p / 4)); } - // Write frame into the encoder - // if it's the first frame, also add what will be the global palette - - // all subsequent frames will just use the global palette - gif.writeFrame(indexedFrame, width_pd, height_pd, { - delay: 20, - transparent: true, - transparentIndex: transparentIndex, - dispose: 1 - }); } + // we decide on one of this colors to be fully transparent + // Apply palette to RGBA data to get an indexed bitmap + const indexedFrame = applyPalette(currFramePixels, globalPalette, { + format + }); + const transparentIndex = indexedFrame[matchingPixelsInFrames[0]]; + + for (let mp = 0; mp < matchingPixelsInFrames.length; mp++) { + let samePixelIndex = matchingPixelsInFrames[mp]; + // here, we overwrite whatever color this pixel was assigned to + // with the color that we decided we are going to use as transparent. + // down in writeFrame we are going to tell the encoder that whenever + // it runs into "transparentIndex", just dig a hole there allowing to + // see through what was in the frame before it. + indexedFrame[samePixelIndex] = transparentIndex; + } + // Write frame into the encoder + + gif.writeFrame(indexedFrame, width_pd, height_pd, { + delay: 20, + transparent: true, + transparentIndex: transparentIndex, + dispose: 1 + }); print('Frame: ' + i.toString()); // this just makes the process asynchronous, preventing From 647a7ca329e7e4da7e7544b70cf264f1510de002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 24 Jul 2022 09:47:32 +0200 Subject: [PATCH 30/61] add underscore to custom functions --- src/image/loading_displaying.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index bb17df7930..267ed71227 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -271,7 +271,7 @@ p5.prototype.saveGif = async function(...args) { busy sketches or low end devices might take longer to render some frames. So we just wait for the frame to be drawn and immediately save it to a buffer and continue - */ + */ redraw(); const data = this.drawingContext.getImageData(0, 0, width_pd, height_pd) @@ -289,7 +289,7 @@ p5.prototype.saveGif = async function(...args) { const format = 'rgb444'; // calculate the global palette for this set of frames - const globalPalette = generateGlobalPalette(frames, format); + const globalPalette = _generateGlobalPalette(frames, format); // we are going to iterate the frames in pairs, n-1 and n for (let i = 0; i < frames.length; i++) { @@ -323,7 +323,7 @@ p5.prototype.saveGif = async function(...args) { lastFramePixels[p + 2], lastFramePixels[p + 3] ]; - if (pixelEquals(currPixel, lastPixel)) { + if (_pixelEquals(currPixel, lastPixel)) { matchingPixelsInFrames.push(parseInt(p / 4)); } } @@ -372,7 +372,7 @@ p5.prototype.saveGif = async function(...args) { p5.prototype.downloadFile(blob, fileName, extension); }; -function generateGlobalPalette(frames, format) { +function _generateGlobalPalette(frames, format) { // for each frame, we'll keep track of the count of // every unique color. that is: how many times does // this particular color appear in every frame? @@ -410,7 +410,7 @@ function generateGlobalPalette(frames, format) { return colorsSortedByFreq.splice(0, 256); } -function pixelEquals(a, b) { +function _pixelEquals(a, b) { return ( Array.isArray(a) && Array.isArray(b) && From f222c9b91d20cddac99754b3e1bff19b9cab00c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 24 Jul 2022 10:53:56 +0200 Subject: [PATCH 31/61] solve transparency bug --- src/image/loading_displaying.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 267ed71227..ec2c6c82da 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -265,6 +265,19 @@ p5.prototype.saveGif = async function(...args) { // We first take every frame that we are going to use for the animation let frames = []; + + let p = undefined; + if (document.getElementById('progressBar') !== null) + document.getElementById('progressBar').remove(); + + p = createP(''); + p.id('progressBar'); + + p.style('font-size', '16px'); + p.style('font-family', 'Montserrat'); + p.style('background-color', '#ffffffa0'); + p.position(0, 0); + while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -280,6 +293,12 @@ p5.prototype.saveGif = async function(...args) { frames.push(data); count++; + p.html( + 'Saved frame ' + + frames.length.toString() + + ' out of ' + + nFrames.toString() + ); await new Promise(resolve => setTimeout(resolve, 0)); } console.info('Frames processed, encoding gif. This may take a while...'); @@ -332,16 +351,15 @@ p5.prototype.saveGif = async function(...args) { const indexedFrame = applyPalette(currFramePixels, globalPalette, { format }); - const transparentIndex = indexedFrame[matchingPixelsInFrames[0]]; + const transparentIndex = currFramePixels[matchingPixelsInFrames[0]]; - for (let mp = 0; mp < matchingPixelsInFrames.length; mp++) { - let samePixelIndex = matchingPixelsInFrames[mp]; + for (let mp of matchingPixelsInFrames) { // here, we overwrite whatever color this pixel was assigned to // with the color that we decided we are going to use as transparent. // down in writeFrame we are going to tell the encoder that whenever // it runs into "transparentIndex", just dig a hole there allowing to // see through what was in the frame before it. - indexedFrame[samePixelIndex] = transparentIndex; + indexedFrame[mp] = transparentIndex; } // Write frame into the encoder @@ -351,7 +369,10 @@ p5.prototype.saveGif = async function(...args) { transparentIndex: transparentIndex, dispose: 1 }); - print('Frame: ' + i.toString()); + + p.html( + 'Rendered frame ' + i.toString() + ' out of ' + nFrames.toString() + ); // this just makes the process asynchronous, preventing // that the encoding locks up the browser @@ -369,6 +390,7 @@ p5.prototype.saveGif = async function(...args) { type: 'image/gif' }); + p.html('Done. Downloading!🌸'); p5.prototype.downloadFile(blob, fileName, extension); }; From 592bb79379ba97bfecc830bdfa5abc82911b828a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Tue, 26 Jul 2022 10:55:45 +0200 Subject: [PATCH 32/61] added babel plugin before uglify for ES6 support --- Gruntfile.js | 18 ++++++++++++++++-- package-lock.json | 5 +++++ package.json | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 2953b17d17..665913a193 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -259,6 +259,16 @@ module.exports = grunt => { } } }, + babel: { + options: { + presets: ['@babel/preset-env'] + }, + dist: { + files: { + 'lib/p5.pre-min.js': 'lib/p5.js' + } + } + }, // This minifies the javascript into a single file and adds a banner to the // front of the file. @@ -274,8 +284,8 @@ module.exports = grunt => { }, dist: { files: { - 'lib/p5.min.js': 'lib/p5.pre-min.js', - 'lib/modules/p5Custom.min.js': 'lib/modules/p5Custom.pre-min.js' + 'lib/p5.min.js': ['lib/p5.pre-min.js'], + 'lib/modules/p5Custom.min.js': ['lib/modules/p5Custom.pre-min.js'] } } }, @@ -511,10 +521,14 @@ module.exports = grunt => { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-simple-nyc'); + //this library converts the ES6 JS to ES5 so it can be properly minified + grunt.loadNpmTasks('grunt-babel'); + // Create the multitasks. grunt.registerTask('build', [ 'browserify', 'browserify:min', + 'babel', 'uglify', 'browserify:test' ]); diff --git a/package-lock.json b/package-lock.json index 72d7ac4ea0..5f9af608ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6827,6 +6827,11 @@ } } }, + "grunt-babel": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/grunt-babel/-/grunt-babel-8.0.0.tgz", + "integrity": "sha512-WuiZFvGzcyzlEoPIcY1snI234ydDWeWWV5bpnB7PZsOLHcDsxWKnrR1rMWEUsbdVPPjvIirwFNsuo4CbJmsdFQ==" + }, "grunt-cli": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.3.2.tgz", diff --git a/package.json b/package.json index f1d46de5ab..9dda281337 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "grunt-mocha-test": "^0.13.3", "grunt-newer": "^1.1.0", "grunt-simple-nyc": "^3.0.1", + "grunt-babel": "^8.0.0", "html-entities": "^1.3.1", "husky": "^4.2.3", "i18next": "^19.0.2", @@ -155,7 +156,6 @@ "not dead" ], "author": "", - "dependencies": {}, "husky": { "hooks": { "pre-commit": "lint-staged" From 18aabac1a923e6e297e82000bc66f5cab294632f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Tue, 26 Jul 2022 10:56:12 +0200 Subject: [PATCH 33/61] improved user feedback through p html elements --- lib/empty-example/index.html | 2 +- lib/empty-example/sketch.js | 2 +- src/image/loading_displaying.js | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/empty-example/index.html b/lib/empty-example/index.html index 788b9f3ee3..54b1bfdfe2 100644 --- a/lib/empty-example/index.html +++ b/lib/empty-example/index.html @@ -12,7 +12,7 @@ background-color: #1b1b1b; } - + diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 844b6b4506..7a53b24409 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -237,7 +237,7 @@ function keyPressed() { switch (key) { // pressing the 's' key case 's': - saveGif('mySketch', 6); + saveGif('mySketch', 2); break; // pressing the '0' key diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index ec2c6c82da..ef8688a573 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -252,10 +252,11 @@ p5.prototype.saveGif = async function(...args) { // we start on the frame set by the delay argument frameCount = nFramesDelay; - console.info( - 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' - ); + // console.info( + // 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' + // ); + const lastPixelDensity = this._pixelDensity; pixelDensity(1); const pd = this._pixelDensity; @@ -266,11 +267,10 @@ p5.prototype.saveGif = async function(...args) { // We first take every frame that we are going to use for the animation let frames = []; - let p = undefined; if (document.getElementById('progressBar') !== null) document.getElementById('progressBar').remove(); - p = createP(''); + let p = createP(''); p.id('progressBar'); p.style('font-size', '16px'); @@ -301,7 +301,11 @@ p5.prototype.saveGif = async function(...args) { ); await new Promise(resolve => setTimeout(resolve, 0)); } - console.info('Frames processed, encoding gif. This may take a while...'); + // console.info('Frames processed, encoding gif. This may take a while...'); + p.html('Frames processed, encoding gif. This may take a while...'); + + loop(); + pixelDensity(lastPixelDensity); // create the gif encoder and the colorspace format const gif = GIFEncoder(); @@ -381,8 +385,6 @@ p5.prototype.saveGif = async function(...args) { gif.finish(); - loop(); - // Get a direct typed array view into the buffer to avoid copying it const buffer = gif.bytesView(); const extension = 'gif'; @@ -403,7 +405,7 @@ function _generateGlobalPalette(frames, format) { // calculate the frequency table for the colors let colorFreq = {}; for (let f of frames) { - let currPalette = quantize(f, 1024, { format }); + let currPalette = quantize(f, 64, { format }); for (let color of currPalette) { // colors are in the format [r, g, b, (a)], as in [255, 127, 45, 255] // we'll convert the array to its string representation so it can be used as an index! From b0e07dc7d910f7694eded7dac263323fb464beef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 4 Aug 2022 10:39:45 +0200 Subject: [PATCH 34/61] add back old saveGif function as encodeAndDownloadGif --- src/image/image.js | 227 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/src/image/image.js b/src/image/image.js index 50cc364da5..2f69b1c969 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -184,6 +184,233 @@ p5.prototype.saveCanvas = function() { }, mimeType); }; +// this is the old saveGif, left here for compatibility purposes +p5.prototype.encodeAndDownloadGif = function(pImg, filename) { + const props = pImg.gifProperties; + + //convert loopLimit back into Netscape Block formatting + let loopLimit = props.loopLimit; + if (loopLimit === 1) { + loopLimit = null; + } else if (loopLimit === null) { + loopLimit = 0; + } + const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames); + + const allFramesPixelColors = []; + + // Used to determine the occurrence of unique palettes and the frames + // which use them + const paletteFreqsAndFrames = {}; + + // Pass 1: + //loop over frames and get the frequency of each palette + for (let i = 0; i < props.numFrames; i++) { + const paletteSet = new Set(); + const data = props.frames[i].image.data; + const dataLength = data.length; + // The color for each pixel in this frame ( for easier lookup later ) + const pixelColors = new Uint32Array(pImg.width * pImg.height); + for (let j = 0, k = 0; j < dataLength; j += 4, k++) { + const r = data[j + 0]; + const g = data[j + 1]; + const b = data[j + 2]; + const color = (r << 16) | (g << 8) | (b << 0); + paletteSet.add(color); + + // What color does this pixel have in this frame ? + pixelColors[k] = color; + } + + // A way to put use the entire palette as an object key + const paletteStr = [...paletteSet].sort().toString(); + if (paletteFreqsAndFrames[paletteStr] === undefined) { + paletteFreqsAndFrames[paletteStr] = { freq: 1, frames: [i] }; + } else { + paletteFreqsAndFrames[paletteStr].freq += 1; + paletteFreqsAndFrames[paletteStr].frames.push(i); + } + + allFramesPixelColors.push(pixelColors); + } + + let framesUsingGlobalPalette = []; + + // Now to build the global palette + // Sort all the unique palettes in descending order of their occurrence + const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function( + a, + b + ) { + return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq; + }); + + // The initial global palette is the one with the most occurrence + const globalPalette = palettesSortedByFreq[0] + .split(',') + .map(a => parseInt(a)); + + framesUsingGlobalPalette = framesUsingGlobalPalette.concat( + paletteFreqsAndFrames[globalPalette].frames + ); + + const globalPaletteSet = new Set(globalPalette); + + // Build a more complete global palette + // Iterate over the remaining palettes in the order of + // their occurrence and see if the colors in this palette which are + // not in the global palette can be added there, while keeping the length + // of the global palette <= 256 + for (let i = 1; i < palettesSortedByFreq.length; i++) { + const palette = palettesSortedByFreq[i].split(',').map(a => parseInt(a)); + + const difference = palette.filter(x => !globalPaletteSet.has(x)); + if (globalPalette.length + difference.length <= 256) { + for (let j = 0; j < difference.length; j++) { + globalPalette.push(difference[j]); + globalPaletteSet.add(difference[j]); + } + + // All frames using this palette now use the global palette + framesUsingGlobalPalette = framesUsingGlobalPalette.concat( + paletteFreqsAndFrames[palettesSortedByFreq[i]].frames + ); + } + } + + framesUsingGlobalPalette = new Set(framesUsingGlobalPalette); + + // Build a lookup table of the index of each color in the global palette + // Maps a color to its index + const globalIndicesLookup = {}; + for (let i = 0; i < globalPalette.length; i++) { + if (!globalIndicesLookup[globalPalette[i]]) { + globalIndicesLookup[globalPalette[i]] = i; + } + } + + // force palette to be power of 2 + let powof2 = 1; + while (powof2 < globalPalette.length) { + powof2 <<= 1; + } + globalPalette.length = powof2; + + // global opts + const opts = { + loop: loopLimit, + palette: new Uint32Array(globalPalette) + }; + const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts); + let previousFrame = {}; + + // Pass 2 + // Determine if the frame needs a local palette + // Also apply transparency optimization. This function will often blow up + // the size of a GIF if not for transparency. If a pixel in one frame has + // the same color in the previous frame, that pixel can be marked as + // transparent. We decide one particular color as transparent and make all + // transparent pixels take this color. This helps in later in compression. + for (let i = 0; i < props.numFrames; i++) { + const localPaletteRequired = !framesUsingGlobalPalette.has(i); + const palette = localPaletteRequired ? [] : globalPalette; + const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height); + + // Lookup table mapping color to its indices + const colorIndicesLookup = {}; + + // All the colors that cannot be marked transparent in this frame + const cannotBeTransparent = new Set(); + + for (let k = 0; k < allFramesPixelColors[i].length; k++) { + const color = allFramesPixelColors[i][k]; + if (localPaletteRequired) { + if (colorIndicesLookup[color] === undefined) { + colorIndicesLookup[color] = palette.length; + palette.push(color); + } + pixelPaletteIndex[k] = colorIndicesLookup[color]; + } else { + pixelPaletteIndex[k] = globalIndicesLookup[color]; + } + + if (i > 0) { + // If even one pixel of this color has changed in this frame + // from the previous frame, we cannot mark it as transparent + if (allFramesPixelColors[i - 1][k] !== color) { + cannotBeTransparent.add(color); + } + } + } + + const frameOpts = {}; + + // Transparency optimization + const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a)); + if (canBeTransparent.length > 0) { + // Select a color to mark as transparent + const transparent = canBeTransparent[0]; + const transparentIndex = localPaletteRequired + ? colorIndicesLookup[transparent] + : globalIndicesLookup[transparent]; + if (i > 0) { + for (let k = 0; k < allFramesPixelColors[i].length; k++) { + // If this pixel in this frame has the same color in previous frame + if (allFramesPixelColors[i - 1][k] === allFramesPixelColors[i][k]) { + pixelPaletteIndex[k] = transparentIndex; + } + } + frameOpts.transparent = transparentIndex; + // If this frame has any transparency, do not dispose the previous frame + previousFrame.frameOpts.disposal = 1; + } + } + frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting + if (localPaletteRequired) { + // force palette to be power of 2 + let powof2 = 1; + while (powof2 < palette.length) { + powof2 <<= 1; + } + palette.length = powof2; + frameOpts.palette = new Uint32Array(palette); + } + if (i > 0) { + // add the frame that came before the current one + gifWriter.addFrame( + 0, + 0, + pImg.width, + pImg.height, + previousFrame.pixelPaletteIndex, + previousFrame.frameOpts + ); + } + // previous frame object should now have details of this frame + previousFrame = { + pixelPaletteIndex, + frameOpts + }; + } + + previousFrame.frameOpts.disposal = 1; + // add the last frame + gifWriter.addFrame( + 0, + 0, + pImg.width, + pImg.height, + previousFrame.pixelPaletteIndex, + previousFrame.frameOpts + ); + + const extension = 'gif'; + const blob = new Blob([buffer.slice(0, gifWriter.end())], { + type: 'image/gif' + }); + p5.prototype.downloadFile(blob, filename, extension); +}; + /** * Capture a sequence of frames that can be used to create a movie. * Accepts a callback. For example, you may wish to send the frames From 540b2f2198449a07ef9bd56fb62afd71a1daa86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 4 Aug 2022 10:40:14 +0200 Subject: [PATCH 35/61] uncomment omggif import --- src/image/image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image/image.js b/src/image/image.js index 2f69b1c969..52cac37870 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -10,7 +10,7 @@ * for drawing images to the main display canvas. */ import p5 from '../core/main'; -// import omggif from 'omggif'; +import omggif from 'omggif'; /** * Creates a new p5.Image (the datatype for storing images). This provides a From 571f8a52345d2a8ba172e59bded25a6f7c95ec9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 4 Aug 2022 11:51:41 +0200 Subject: [PATCH 36/61] change ecmaVersion to accept async --- Gruntfile.js | 4 ++-- src/image/loading_displaying.js | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 52f437967c..5ea366e622 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -150,7 +150,7 @@ module.exports = grunt => { source: { options: { parserOptions: { - ecmaVersion: 5 + ecmaVersion: 8 } }, src: ['src/**/*.js'] @@ -163,7 +163,7 @@ module.exports = grunt => { 'eslint-samples': { options: { parserOptions: { - ecmaVersion: 6 + ecmaVersion: 8 }, format: 'unix' }, diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index e3a2e9a6bc..b1b1812150 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -256,7 +256,7 @@ p5.prototype.saveGif = async function(...args) { // ); const lastPixelDensity = this._pixelDensity; - pixelDensity(1); + this.pixelDensity(1); const pd = this._pixelDensity; // width and height based on (p)ixel (d)ensity @@ -269,7 +269,7 @@ p5.prototype.saveGif = async function(...args) { if (document.getElementById('progressBar') !== null) document.getElementById('progressBar').remove(); - let p = createP(''); + let p = this.createP(''); p.id('progressBar'); p.style('font-size', '16px'); @@ -303,8 +303,8 @@ p5.prototype.saveGif = async function(...args) { // console.info('Frames processed, encoding gif. This may take a while...'); p.html('Frames processed, encoding gif. This may take a while...'); - loop(); - pixelDensity(lastPixelDensity); + this.loop(); + this.pixelDensity(lastPixelDensity); // create the gif encoder and the colorspace format const gif = GIFEncoder(); @@ -393,6 +393,7 @@ p5.prototype.saveGif = async function(...args) { frames = []; this.loop(); + p.html('Done. Downloading!🌸'); p5.prototype.downloadFile(blob, fileName, extension); }; From 73acfb183744179ea03a5f8f8e6bd1778205b6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sat, 6 Aug 2022 10:39:32 +0200 Subject: [PATCH 37/61] renamed functions across the repo --- lib/empty-example/sketch.js | 1 + src/image/image.js | 2 ++ src/image/loading_displaying.js | 18 ++++-------------- src/image/p5.Image.js | 2 +- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index 7a53b24409..d9e79ed6b5 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -102,6 +102,7 @@ function setup() { gen_transparency = random(20, 255); background(24); + // saveGif('mySketch', 2); } function draw() { diff --git a/src/image/image.js b/src/image/image.js index 6d5c9adb3a..d2f0d82c99 100644 --- a/src/image/image.js +++ b/src/image/image.js @@ -185,6 +185,8 @@ p5.prototype.saveCanvas = function() { }; // this is the old saveGif, left here for compatibility purposes +// the only place I found it being used was on image/p5.Image.js, on the +// save function. that has been changed to use this function. p5.prototype.encodeAndDownloadGif = function(pImg, filename) { const props = pImg.gifProperties; diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index b1b1812150..d10a79b786 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -251,17 +251,8 @@ p5.prototype.saveGif = async function(...args) { // we start on the frame set by the delay argument frameCount = nFramesDelay; - // console.info( - // 'Processing ' + nFrames + ' frames with ' + delay + ' seconds of delay...' - // ); - const lastPixelDensity = this._pixelDensity; this.pixelDensity(1); - const pd = this._pixelDensity; - - // width and height based on (p)ixel (d)ensity - const width_pd = this.width * pd; - const height_pd = this.height * pd; // We first take every frame that we are going to use for the animation let frames = []; @@ -286,7 +277,7 @@ p5.prototype.saveGif = async function(...args) { */ this.redraw(); - const data = this.drawingContext.getImageData(0, 0, width_pd, height_pd) + const data = this.drawingContext.getImageData(0, 0, this.width, this.height) .data; frames.push(data); @@ -300,7 +291,6 @@ p5.prototype.saveGif = async function(...args) { ); await new Promise(resolve => setTimeout(resolve, 0)); } - // console.info('Frames processed, encoding gif. This may take a while...'); p.html('Frames processed, encoding gif. This may take a while...'); this.loop(); @@ -317,7 +307,7 @@ p5.prototype.saveGif = async function(...args) { for (let i = 0; i < frames.length; i++) { if (i === 0) { const indexedFrame = applyPalette(frames[i], globalPalette, { format }); - gif.writeFrame(indexedFrame, width_pd, height_pd, { + gif.writeFrame(indexedFrame, this.width, this.height, { palette: globalPalette, delay: 20, dispose: 1 @@ -354,7 +344,7 @@ p5.prototype.saveGif = async function(...args) { const indexedFrame = applyPalette(currFramePixels, globalPalette, { format }); - const transparentIndex = currFramePixels[matchingPixelsInFrames[0]]; + const transparentIndex = indexedFrame[matchingPixelsInFrames[0]]; for (let mp of matchingPixelsInFrames) { // here, we overwrite whatever color this pixel was assigned to @@ -366,7 +356,7 @@ p5.prototype.saveGif = async function(...args) { } // Write frame into the encoder - gif.writeFrame(indexedFrame, width_pd, height_pd, { + gif.writeFrame(indexedFrame, this.width, this.height, { delay: 20, transparent: true, transparentIndex: transparentIndex, diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js index 97253ec1ad..d0a6b469e3 100644 --- a/src/image/p5.Image.js +++ b/src/image/p5.Image.js @@ -890,7 +890,7 @@ p5.Image.prototype.isModified = function() { */ p5.Image.prototype.save = function(filename, extension) { if (this.gifProperties) { - p5.prototype.saveGif(this, filename); + p5.prototype.encodeAndDownloadGif(this, filename); } else { p5.prototype.saveCanvas(this.canvas, filename, extension); } From ae03db0c62863387933f5dfd88ed3cb7dfbde411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 8 Aug 2022 12:28:04 +0200 Subject: [PATCH 38/61] better global palette and fixed index bug! --- src/image/loading_displaying.js | 56 ++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index d10a79b786..8ccc3c4eca 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -302,6 +302,7 @@ p5.prototype.saveGif = async function(...args) { // calculate the global palette for this set of frames const globalPalette = _generateGlobalPalette(frames, format); + const transparentIndex = globalPalette.length - 1; // we are going to iterate the frames in pairs, n-1 and n for (let i = 0; i < frames.length; i++) { @@ -344,7 +345,8 @@ p5.prototype.saveGif = async function(...args) { const indexedFrame = applyPalette(currFramePixels, globalPalette, { format }); - const transparentIndex = indexedFrame[matchingPixelsInFrames[0]]; + + // console.log(transparentIndex, globalPalette[transparentIndex]); for (let mp of matchingPixelsInFrames) { // here, we overwrite whatever color this pixel was assigned to @@ -384,7 +386,7 @@ p5.prototype.saveGif = async function(...args) { frames = []; this.loop(); - p.html('Done. Downloading!🌸'); + p.html('Done. Downloading your gif!🌸'); p5.prototype.downloadFile(blob, fileName, extension); }; @@ -396,16 +398,30 @@ function _generateGlobalPalette(frames, format) { // calculate the frequency table for the colors let colorFreq = {}; - for (let f of frames) { - let currPalette = quantize(f, 64, { format }); - for (let color of currPalette) { + for (let f = 0; f < frames.length; f++) { + /** + * here, we use the quantize function in a rather unusual way. + * the quantize function will return a subset of colors for + * the given array of pixels. this is kinda like a "sum up" of + * the most important colors in the image, which will prevent us + * from exhaustively analyzing every pixel from every frame. + * + * in this case, we can just analyze the subset of the most + * important colors from each frame, which is actually more + * than enough for it to work properly. + */ + let currPalette = quantize(frames[f], 256, { format }); + + for (let c = 0; c < currPalette.length; c++) { // colors are in the format [r, g, b, (a)], as in [255, 127, 45, 255] // we'll convert the array to its string representation so it can be used as an index! - color = color.toString(); - if (colorFreq[color] === undefined) { - colorFreq[color] = { count: 1 }; + + let colorStr = currPalette[c].toString(); + + if (colorFreq[colorStr] === undefined) { + colorFreq[colorStr] = 1; } else { - colorFreq[color].count += 1; + colorFreq[colorStr] = colorFreq[colorStr] + 1; } } } @@ -413,14 +429,24 @@ function _generateGlobalPalette(frames, format) { // at this point colorFreq is a dict with {color: count}, // telling us how many times each color appears in the whole animation + // we create a new view into the dictionary as an array, in the form + // ['color', count] + let dictItems = Object.keys(colorFreq).map(function(key) { + return [key, colorFreq[key]]; + }); + + // with that view, we can now properly sort the array based + // on the second component of each element + dictItems.sort(function(first, second) { + return second[1] - first[1]; + }); + // we process it undoing the string operation coverting that into - // an array of strings (['255', '127', '45', '255']) and then we convert + // an array of strings (['255', '127', '45']) and then we convert // that again to an array of integers - let colorsSortedByFreq = Object.keys(colorFreq) - .sort((a, b) => { - return colorFreq[b].count - colorFreq[a].count; - }) - .map(c => c.split(',').map(x => parseInt(x))); + let colorsSortedByFreq = dictItems.map(i => + i[0].split(',').map(n => parseInt(n)) + ); // now we simply extract the top 256 colors! return colorsSortedByFreq.splice(0, 256); From 629d7448d4dc0cf562bc9214ab6cc76d04f5a680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 8 Aug 2022 13:01:28 +0200 Subject: [PATCH 39/61] adjust test from saveGif to encodeAndDownloadGif --- test/unit/image/downloading.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/image/downloading.js b/test/unit/image/downloading.js index e4973151e8..19b6caf905 100644 --- a/test/unit/image/downloading.js +++ b/test/unit/image/downloading.js @@ -32,16 +32,16 @@ suite('downloading animated gifs', function() { }); }); - suite('p5.prototype.saveGif', function() { + suite('p5.prototype.encodeAndDownloadGif', function() { test('should be a function', function() { - assert.ok(myp5.saveGif); - assert.typeOf(myp5.saveGif, 'function'); + assert.ok(myp5.encodeAndDownloadGif); + assert.typeOf(myp5.encodeAndDownloadGif, 'function'); }); test('should not throw an error', function() { - myp5.saveGif(myGif); + myp5.encodeAndDownloadGif(myGif); }); testWithDownload('should download a gif', function(blobContainer) { - myp5.saveGif(myGif); + myp5.encodeAndDownloadGif(myGif); let gifBlob = blobContainer.blob; assert.strictEqual(gifBlob.type, 'image/gif'); }); From 0ebf9b712d609132511fde841d314620bef315c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 8 Aug 2022 13:53:04 +0200 Subject: [PATCH 40/61] small styling change --- src/image/loading_displaying.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8ccc3c4eca..840da6f5cb 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -266,6 +266,8 @@ p5.prototype.saveGif = async function(...args) { p.style('font-size', '16px'); p.style('font-family', 'Montserrat'); p.style('background-color', '#ffffffa0'); + p.style('padding', '8px'); + p.style('border-radius', '10px'); p.position(0, 0); while (count < nFrames + nFramesDelay) { From 566474400be8c4293f0abbcf6ae6a87e015bcaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 8 Aug 2022 19:28:14 +0200 Subject: [PATCH 41/61] added support for WEBGL renderer! --- src/image/loading_displaying.js | 62 +++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 840da6f5cb..6ffb029d21 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -270,6 +270,15 @@ p5.prototype.saveGif = async function(...args) { p.style('border-radius', '10px'); p.position(0, 0); + let pixels; + let gl; + if (this.drawingContext instanceof WebGLRenderingContext) { + // if we have a WEBGL context, initialize the pixels array + // and the gl context to use them inside the loop + gl = document.getElementById('defaultCanvas0').getContext('webgl'); + pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4); + } + while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -279,8 +288,29 @@ p5.prototype.saveGif = async function(...args) { */ this.redraw(); - const data = this.drawingContext.getImageData(0, 0, this.width, this.height) - .data; + // depending on the context we'll extract the pixels one way + // or another + let data = undefined; + + if (this.drawingContext instanceof WebGLRenderingContext) { + pixels = new Uint8Array( + gl.drawingBufferWidth * gl.drawingBufferHeight * 4 + ); + gl.readPixels( + 0, + 0, + gl.drawingBufferWidth, + gl.drawingBufferHeight, + gl.RGBA, + gl.UNSIGNED_BYTE, + pixels + ); + + data = _flipPixels(pixels); + } else { + data = this.drawingContext.getImageData(0, 0, this.width, this.height) + .data; + } frames.push(data); count++; @@ -392,6 +422,34 @@ p5.prototype.saveGif = async function(...args) { p5.prototype.downloadFile(blob, fileName, extension); }; +function _flipPixels(pixels) { + // extracting the pixels using readPixels returns + // an upside down image. we have to flip it back + // first. this solution is proposed by gman on + // this stack overflow answer: + // https://stackoverflow.com/questions/41969562/how-can-i-flip-the-result-of-webglrenderingcontext-readpixels + + var halfHeight = parseInt(height / 2); + var bytesPerRow = width * 4; + + // make a temp buffer to hold one row + var temp = new Uint8Array(width * 4); + for (var y = 0; y < halfHeight; ++y) { + var topOffset = y * bytesPerRow; + var bottomOffset = (height - y - 1) * bytesPerRow; + + // make copy of a row on the top half + temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow)); + + // copy a row from the bottom half to the top + pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); + + // copy the copy of the top half row to the bottom half + pixels.set(temp, bottomOffset); + } + return pixels; +} + function _generateGlobalPalette(frames, format) { // for each frame, we'll keep track of the count of // every unique color. that is: how many times does From 02c44be28c3eccc942026e2d767bb602fbb27d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Tue, 9 Aug 2022 17:52:42 +0200 Subject: [PATCH 42/61] created new globalPalette function --- src/image/loading_displaying.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 6ffb029d21..c902b6769c 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -334,6 +334,8 @@ p5.prototype.saveGif = async function(...args) { // calculate the global palette for this set of frames const globalPalette = _generateGlobalPalette(frames, format); + // const globalPalette2 = _generateGlobalPalette2(frames, format); + console.log(globalPalette); const transparentIndex = globalPalette.length - 1; // we are going to iterate the frames in pairs, n-1 and n @@ -392,8 +394,8 @@ p5.prototype.saveGif = async function(...args) { gif.writeFrame(indexedFrame, this.width, this.height, { delay: 20, - transparent: true, - transparentIndex: transparentIndex, + // transparent: true, + // transparentIndex: transparentIndex, dispose: 1 }); @@ -450,6 +452,24 @@ function _flipPixels(pixels) { return pixels; } +// function _generateGlobalPalette2(frames, format) { +// // make an array the size of every possible color in every possible frame +// // that is: width * height * frames. +// let allColors = new Uint8Array(frames.length * frames[0].length); + +// // put every frame one after the other in sequence. +// // this array will hold absolutely every pixel from the animation. +// // the set function on the Uint8Array works super fast tho! +// for (let f = 0; f < frames.length; f++) { +// allColors.set(frames[0], f * frames[0].length); +// } + +// // quantize this massive array into 256 colors and return it! +// let colorPalette = quantize(allColors, 255, { format }); +// colorPalette.push([-1, -1, -1]); +// return colorPalette; +// } + function _generateGlobalPalette(frames, format) { // for each frame, we'll keep track of the count of // every unique color. that is: how many times does @@ -508,6 +528,7 @@ function _generateGlobalPalette(frames, format) { i[0].split(',').map(n => parseInt(n)) ); + console.log(colorsSortedByFreq.splice(0, 256)); // now we simply extract the top 256 colors! return colorsSortedByFreq.splice(0, 256); } From 7557cf8024dcfe34bdae64f489a99572f87ad763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 10 Aug 2022 13:36:13 +0200 Subject: [PATCH 43/61] refactoring and better palette generation --- src/image/loading_displaying.js | 144 ++++++++++++-------------------- 1 file changed, 55 insertions(+), 89 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index c902b6769c..8b0acecff0 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -323,25 +323,26 @@ p5.prototype.saveGif = async function(...args) { ); await new Promise(resolve => setTimeout(resolve, 0)); } - p.html('Frames processed, encoding gif. This may take a while...'); + p.html('Frames processed, generating color palette...'); this.loop(); this.pixelDensity(lastPixelDensity); // create the gif encoder and the colorspace format const gif = GIFEncoder(); - const format = 'rgb444'; // calculate the global palette for this set of frames - const globalPalette = _generateGlobalPalette(frames, format); - // const globalPalette2 = _generateGlobalPalette2(frames, format); - console.log(globalPalette); + const globalPalette = _generateGlobalPalette(frames); + + // the way we designed the palette means we always take the last index for transparency const transparentIndex = globalPalette.length - 1; // we are going to iterate the frames in pairs, n-1 and n for (let i = 0; i < frames.length; i++) { if (i === 0) { - const indexedFrame = applyPalette(frames[i], globalPalette, { format }); + const indexedFrame = applyPalette(frames[i], globalPalette, { + format: 'rgba4444' + }); gif.writeFrame(indexedFrame, this.width, this.height, { palette: globalPalette, delay: 20, @@ -353,7 +354,7 @@ p5.prototype.saveGif = async function(...args) { // matching pixels between frames can be set to full transparency, // kinda digging a "hole" into the frame to see the pixels that where behind it // (which would be the exact same, so not noticeable changes) - // this helps make the file smaller + // this helps make the file quite smaller let currFramePixels = frames[i]; let lastFramePixels = frames[i - 1]; let matchingPixelsInFrames = []; @@ -370,6 +371,8 @@ p5.prototype.saveGif = async function(...args) { lastFramePixels[p + 2], lastFramePixels[p + 3] ]; + + // if the pixels are equal, save this index to be used later if (_pixelEquals(currPixel, lastPixel)) { matchingPixelsInFrames.push(parseInt(p / 4)); } @@ -377,25 +380,24 @@ p5.prototype.saveGif = async function(...args) { // we decide on one of this colors to be fully transparent // Apply palette to RGBA data to get an indexed bitmap const indexedFrame = applyPalette(currFramePixels, globalPalette, { - format + format: 'rgba4444' }); - // console.log(transparentIndex, globalPalette[transparentIndex]); - - for (let mp of matchingPixelsInFrames) { + for (let i = 0; i < matchingPixelsInFrames.length; i++) { // here, we overwrite whatever color this pixel was assigned to // with the color that we decided we are going to use as transparent. // down in writeFrame we are going to tell the encoder that whenever // it runs into "transparentIndex", just dig a hole there allowing to // see through what was in the frame before it. - indexedFrame[mp] = transparentIndex; + let pixelIndex = matchingPixelsInFrames[i]; + indexedFrame[pixelIndex] = transparentIndex; } - // Write frame into the encoder + // Write frame into the encoder gif.writeFrame(indexedFrame, this.width, this.height, { delay: 20, - // transparent: true, - // transparentIndex: transparentIndex, + transparent: true, + transparentIndex: transparentIndex, dispose: 1 }); @@ -452,85 +454,49 @@ function _flipPixels(pixels) { return pixels; } -// function _generateGlobalPalette2(frames, format) { -// // make an array the size of every possible color in every possible frame -// // that is: width * height * frames. -// let allColors = new Uint8Array(frames.length * frames[0].length); - -// // put every frame one after the other in sequence. -// // this array will hold absolutely every pixel from the animation. -// // the set function on the Uint8Array works super fast tho! -// for (let f = 0; f < frames.length; f++) { -// allColors.set(frames[0], f * frames[0].length); -// } - -// // quantize this massive array into 256 colors and return it! -// let colorPalette = quantize(allColors, 255, { format }); -// colorPalette.push([-1, -1, -1]); -// return colorPalette; -// } - -function _generateGlobalPalette(frames, format) { - // for each frame, we'll keep track of the count of - // every unique color. that is: how many times does - // this particular color appear in every frame? - // Then we'll sort the colors and pick the top 256! - - // calculate the frequency table for the colors - let colorFreq = {}; +function _generateGlobalPalette(frames) { + // make an array the size of every possible color in every possible frame + // that is: width * height * frames. + let allColors = new Uint8Array(frames.length * frames[0].length); + + // put every frame one after the other in sequence. + // this array will hold absolutely every pixel from the animation. + // the set function on the Uint8Array works super fast tho! for (let f = 0; f < frames.length; f++) { - /** - * here, we use the quantize function in a rather unusual way. - * the quantize function will return a subset of colors for - * the given array of pixels. this is kinda like a "sum up" of - * the most important colors in the image, which will prevent us - * from exhaustively analyzing every pixel from every frame. - * - * in this case, we can just analyze the subset of the most - * important colors from each frame, which is actually more - * than enough for it to work properly. - */ - let currPalette = quantize(frames[f], 256, { format }); - - for (let c = 0; c < currPalette.length; c++) { - // colors are in the format [r, g, b, (a)], as in [255, 127, 45, 255] - // we'll convert the array to its string representation so it can be used as an index! - - let colorStr = currPalette[c].toString(); - - if (colorFreq[colorStr] === undefined) { - colorFreq[colorStr] = 1; - } else { - colorFreq[colorStr] = colorFreq[colorStr] + 1; - } - } + allColors.set(frames[0], f * frames[0].length); } - // at this point colorFreq is a dict with {color: count}, - // telling us how many times each color appears in the whole animation - - // we create a new view into the dictionary as an array, in the form - // ['color', count] - let dictItems = Object.keys(colorFreq).map(function(key) { - return [key, colorFreq[key]]; - }); - - // with that view, we can now properly sort the array based - // on the second component of each element - dictItems.sort(function(first, second) { - return second[1] - first[1]; + // quantize this massive array into 256 colors and return it! + let colorPalette = quantize(allColors, 256, { + format: 'rgba444', + oneBitAlpha: true }); - // we process it undoing the string operation coverting that into - // an array of strings (['255', '127', '45']) and then we convert - // that again to an array of integers - let colorsSortedByFreq = dictItems.map(i => - i[0].split(',').map(n => parseInt(n)) - ); - - console.log(colorsSortedByFreq.splice(0, 256)); - // now we simply extract the top 256 colors! - return colorsSortedByFreq.splice(0, 256); + // when generating the palette, we have to leave space for 1 of the + // indices to be a random color that does not appear anywhere in our + // animation to use for transparency purposes. So, if the palette is full + // (has 256 colors), we overwrite the last one with a random, fully transparent + // color. Otherwise, we just push a new color into the palette the same way. + + // this guarantees that when using the transparency index, there are no matches + // between some colors of the animation and the "holes" we want to dig on them, + // which would cause pieces of some frames to be transparent and thus look glitchy. + if (colorPalette.length === 256) { + colorPalette[colorPalette.length - 1] = [ + Math.random() * 255, + Math.random() * 255, + Math.random() * 255, + 0 + ]; + } else { + colorPalette.push([ + Math.random() * 255, + Math.random() * 255, + Math.random() * 255, + 0 + ]); + } + return colorPalette; } function _pixelEquals(a, b) { From bed8cfa01c94c03c49757b5ea8ad3f4ce2e93efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 11 Aug 2022 12:59:19 +0200 Subject: [PATCH 44/61] refactor arguments and support for all frameRates! --- src/image/loading_displaying.js | 38 ++++++++++----------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8b0acecff0..963c398e93 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -207,31 +207,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * @alt * animation of a circle moving smoothly diagonally */ -p5.prototype.saveGif = async function(...args) { - // process args - - let fileName; - let seconds; - let delay; - - // this section takes care of parsing and processing - // the arguments in the correct format - switch (args.length) { - case 2: - fileName = args[0]; - seconds = args[1]; - break; - case 3: - fileName = args[0]; - seconds = args[1]; - delay = args[2]; - break; - } - - if (!delay) { - delay = 0; - } - +p5.prototype.saveGif = async function(fileName, seconds = 3, delay = 0) { // get the project's framerate // if it is undefined or some non useful value, assume it's 60 let _frameRate = this._targetFrameRate; @@ -239,6 +215,14 @@ p5.prototype.saveGif = async function(...args) { _frameRate = 60; } + // calculate delay based on frameRate + let gifFrameDelay = 1 / _frameRate * 1000; + + // constrain it to be always greater than 20, + // otherwise it won't work in some browsers and systems + // reference: https://stackoverflow.com/questions/64473278/gif-frame-duration-seems-slower-than-expected + gifFrameDelay = gifFrameDelay < 20 ? 20 : gifFrameDelay; + // because the input was in seconds, we now calculate // how many frames those seconds translate to let nFrames = Math.ceil(seconds * _frameRate); @@ -345,7 +329,7 @@ p5.prototype.saveGif = async function(...args) { }); gif.writeFrame(indexedFrame, this.width, this.height, { palette: globalPalette, - delay: 20, + delay: gifFrameDelay, dispose: 1 }); continue; @@ -395,7 +379,7 @@ p5.prototype.saveGif = async function(...args) { // Write frame into the encoder gif.writeFrame(indexedFrame, this.width, this.height, { - delay: 20, + delay: gifFrameDelay, transparent: true, transparentIndex: transparentIndex, dispose: 1 From 79452ecff64d11f14d73a3c254cba25db896628b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 11 Aug 2022 13:55:50 +0200 Subject: [PATCH 45/61] change typing in documentation example --- src/image/loading_displaying.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 963c398e93..a1093e5f59 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -171,8 +171,8 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * * @method saveGif * @param {String} filename File name of your gif - * @param {String} duration Duration in seconds that you wish to capture from your sketch - * @param {String} delay Duration in seconds that you wish to wait before starting to capture + * @param {Number} duration Duration in seconds that you wish to capture from your sketch + * @param {Number} delay Duration in seconds that you wish to wait before starting to capture * * @example *
From 5b582000403012633c73461182a912bb11ad7271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 11 Aug 2022 13:56:50 +0200 Subject: [PATCH 46/61] add parameter validation through FES --- src/image/loading_displaying.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index a1093e5f59..c52c7dbc96 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -208,6 +208,8 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * animation of a circle moving smoothly diagonally */ p5.prototype.saveGif = async function(fileName, seconds = 3, delay = 0) { + // validate parameters + p5._validateParameters('saveGif', arguments); // get the project's framerate // if it is undefined or some non useful value, assume it's 60 let _frameRate = this._targetFrameRate; From 547b533d0c73dfa0bd3d81adeda832f8f9b3cd9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Thu, 11 Aug 2022 13:56:58 +0200 Subject: [PATCH 47/61] wrote initial tests --- test/unit/image/downloading.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/unit/image/downloading.js b/test/unit/image/downloading.js index 19b6caf905..67419dfe02 100644 --- a/test/unit/image/downloading.js +++ b/test/unit/image/downloading.js @@ -320,3 +320,33 @@ suite('p5.prototype.saveFrames', function() { }); }); }); + +suite('p5.prototype.saveGif', function() { + setup(function(done) { + new p5(function(p) { + p.setup = function() { + myp5 = p; + p.createCanvas(10, 10); + done(); + }; + }); + }); + + teardown(function() { + myp5.remove(); + }); + + test('should be a function', function() { + assert.ok(myp5.saveGif); + assert.typeOf(myp5.saveGif, 'function'); + }); + test('should not throw an error', function() { + myp5.saveGif('myGif', 3, 2); + }); + testWithDownload('should download a GIF', async function(blobContainer) { + myp5.saveGif(myGif, 3, 2); + await waitForBlob(blobContainer); + let gifBlob = blobContainer.blob; + assert.strictEqual(gifBlob.type, 'image/gif'); + }); +}); From 57158e5ae5aaabaf0cca6bebfa4e9a2248135cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 17 Aug 2022 09:26:44 +0200 Subject: [PATCH 48/61] fix use of var in line 234 --- src/image/loading_displaying.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index c52c7dbc96..3fe49dd81e 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -231,7 +231,7 @@ p5.prototype.saveGif = async function(fileName, seconds = 3, delay = 0) { let nFramesDelay = Math.ceil(delay * _frameRate); // initialize variables for the frames processing - var count = nFramesDelay; + let count = nFramesDelay; this.noLoop(); // we start on the frame set by the delay argument @@ -360,7 +360,7 @@ p5.prototype.saveGif = async function(fileName, seconds = 3, delay = 0) { // if the pixels are equal, save this index to be used later if (_pixelEquals(currPixel, lastPixel)) { - matchingPixelsInFrames.push(parseInt(p / 4)); + matchingPixelsInFrames.push(p / 4); } } // we decide on one of this colors to be fully transparent From 5fd17662403afc7c974b3ecce9d757306c68590d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 17 Aug 2022 09:28:25 +0200 Subject: [PATCH 49/61] remove initialization of arguments --- src/image/loading_displaying.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 3fe49dd81e..48d991aba1 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -207,9 +207,10 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * @alt * animation of a circle moving smoothly diagonally */ -p5.prototype.saveGif = async function(fileName, seconds = 3, delay = 0) { +p5.prototype.saveGif = async function(fileName, seconds, delay) { // validate parameters p5._validateParameters('saveGif', arguments); + // get the project's framerate // if it is undefined or some non useful value, assume it's 60 let _frameRate = this._targetFrameRate; From ebd2d6146bd80b44897733798a8347157468afc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sat, 20 Aug 2022 13:22:06 +0200 Subject: [PATCH 50/61] parsing arguments and adding options object --- src/image/loading_displaying.js | 72 ++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 48d991aba1..077611c4d7 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -163,16 +163,26 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * Generates a gif of your current animation and downloads it to your computer! * * The duration argument specifies how many seconds you want to record from your animation. - * This value is then converted to the necessary number of frames to generate it. + * This value is then converted to the necessary number of frames to generate it, depending + * on the value of units. More on that on the next paragraph. * - * With the delay argument, you can tell the function to skip the first `delay` seconds - * of the animation, and then download the `duration` next seconds. This means that regardless - * of the value of `delay`, your gif will always be `duration` seconds long. + * An optional object that can contain two more arguments: delay (number) and units (string). + * + * `delay`, specifying how much time we should wait before recording + * + * `units`, a string that can be either 'seconds' or 'frames'. By default it's 'seconds'. + * + * `units` specifies how the duration and delay arguments will behave. + * If 'seconds', these arguments will correspond to seconds, meaning that 3 seconds worth of animation + * will be created. If 'frames', the arguments now correspond to the number of frames you want your + * animation to be, if you are very sure of this number. * * @method saveGif * @param {String} filename File name of your gif * @param {Number} duration Duration in seconds that you wish to capture from your sketch - * @param {Number} delay Duration in seconds that you wish to wait before starting to capture + * @param {Object} options An optional object that can contain two more arguments: delay, specifying + * how much time we should wait before recording, and units, a string that can be either 'seconds' or + * 'frames'. By default it's 'seconds'. * * @example *
@@ -207,18 +217,47 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * @alt * animation of a circle moving smoothly diagonally */ -p5.prototype.saveGif = async function(fileName, seconds, delay) { - // validate parameters +p5.prototype.saveGif = async function( + fileName, + duration, + { delay = 0, units = 'seconds' } +) { + // validate parameters to throw friendly error p5._validateParameters('saveGif', arguments); + // throwing exception for tests + let delayOption = arguments[2].delay; + let unitsOption = arguments[2].units; + + if (typeof fileName !== String) + throw TypeError('saveGif(): First argument should be a string'); + if (typeof duration !== Number) + throw TypeError('saveGif(): Second argument should be a number'); + if (typeof arguments[2] !== Object) + throw TypeError('saveGif(): Third argument should be an object'); + if (typeof delayOption !== Number) { + throw TypeError( + 'saveGif() options: first option "delay" should be a number' + ); + } + if (unitsOption !== 'seconds' || unitsOption !== 'frames') { + throw TypeError( + 'saveGif() options: second option "units" should either be "seconds" or "frames"' + ); + } + // get the project's framerate - // if it is undefined or some non useful value, assume it's 60 let _frameRate = this._targetFrameRate; + // if it is undefined or some non useful value, assume it's 60 if (_frameRate === Infinity || _frameRate === undefined || _frameRate === 0) { _frameRate = 60; } - // calculate delay based on frameRate + // calculate frame delay based on frameRate + + // this delay has nothing to do with the + // delay in options, but rather is the delay + // we have to specify to the gif encoder between frames. let gifFrameDelay = 1 / _frameRate * 1000; // constrain it to be always greater than 20, @@ -226,15 +265,14 @@ p5.prototype.saveGif = async function(fileName, seconds, delay) { // reference: https://stackoverflow.com/questions/64473278/gif-frame-duration-seems-slower-than-expected gifFrameDelay = gifFrameDelay < 20 ? 20 : gifFrameDelay; - // because the input was in seconds, we now calculate - // how many frames those seconds translate to - let nFrames = Math.ceil(seconds * _frameRate); - let nFramesDelay = Math.ceil(delay * _frameRate); + // check the mode we are in and how many frames + // that duration translates to + const nFrames = units === 'seconds' ? duration * _frameRate : duration; + const nFramesDelay = units === 'seconds' ? delay * _frameRate : delay; - // initialize variables for the frames processing + // initialize variables for the frames processing let count = nFramesDelay; - this.noLoop(); // we start on the frame set by the delay argument frameCount = nFramesDelay; @@ -266,6 +304,9 @@ p5.prototype.saveGif = async function(fileName, seconds, delay) { pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4); } + // stop the loop since we are going to manually redraw + this.noLoop(); + while (count < nFrames + nFramesDelay) { /* we draw the next frame. this is important, since @@ -402,6 +443,7 @@ p5.prototype.saveGif = async function(fileName, seconds, delay) { // Get a direct typed array view into the buffer to avoid copying it const buffer = gif.bytesView(); const extension = 'gif'; + const blob = new Blob([buffer], { type: 'image/gif' }); From 133eafa037790aac193af84866c3669f7d1dc64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 21 Aug 2022 10:03:39 +0200 Subject: [PATCH 51/61] remove unnecessary code and update example --- package-lock.json | 3 ++- src/image/loading_displaying.js | 25 ++----------------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe4acfa173..1a0835fbe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6830,7 +6830,8 @@ "grunt-babel": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/grunt-babel/-/grunt-babel-8.0.0.tgz", - "integrity": "sha512-WuiZFvGzcyzlEoPIcY1snI234ydDWeWWV5bpnB7PZsOLHcDsxWKnrR1rMWEUsbdVPPjvIirwFNsuo4CbJmsdFQ==" + "integrity": "sha512-WuiZFvGzcyzlEoPIcY1snI234ydDWeWWV5bpnB7PZsOLHcDsxWKnrR1rMWEUsbdVPPjvIirwFNsuo4CbJmsdFQ==", + "dev": true }, "grunt-cli": { "version": "1.3.2", diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 077611c4d7..8d0a71e8c3 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -209,7 +209,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * // or keyPressed for example * function mousePressed() { * // this will download the first two seconds of my animation! - * saveGif('mySketch', 2); + * saveGif('mySketch', 2, {units: 'seconds', delay: 0}); * } * *
@@ -225,27 +225,6 @@ p5.prototype.saveGif = async function( // validate parameters to throw friendly error p5._validateParameters('saveGif', arguments); - // throwing exception for tests - let delayOption = arguments[2].delay; - let unitsOption = arguments[2].units; - - if (typeof fileName !== String) - throw TypeError('saveGif(): First argument should be a string'); - if (typeof duration !== Number) - throw TypeError('saveGif(): Second argument should be a number'); - if (typeof arguments[2] !== Object) - throw TypeError('saveGif(): Third argument should be an object'); - if (typeof delayOption !== Number) { - throw TypeError( - 'saveGif() options: first option "delay" should be a number' - ); - } - if (unitsOption !== 'seconds' || unitsOption !== 'frames') { - throw TypeError( - 'saveGif() options: second option "units" should either be "seconds" or "frames"' - ); - } - // get the project's framerate let _frameRate = this._targetFrameRate; // if it is undefined or some non useful value, assume it's 60 @@ -274,7 +253,7 @@ p5.prototype.saveGif = async function( let count = nFramesDelay; // we start on the frame set by the delay argument - frameCount = nFramesDelay; + // frameCount = nFramesDelay; const lastPixelDensity = this._pixelDensity; this.pixelDensity(1); From f42fef88112c3fb422409cd3ec6a8a5b355c99ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 21 Aug 2022 10:04:10 +0200 Subject: [PATCH 52/61] fix lint issue in example --- src/image/loading_displaying.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8d0a71e8c3..5995ec5f64 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -209,7 +209,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * // or keyPressed for example * function mousePressed() { * // this will download the first two seconds of my animation! - * saveGif('mySketch', 2, {units: 'seconds', delay: 0}); + * saveGif('mySketch', 2, { units: 'seconds', delay: 0 }); * } * *
From 7daec8ed88c2735c6155c6aa9000c874ed4d8795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 21 Aug 2022 11:07:52 +0200 Subject: [PATCH 53/61] added more tests! --- test/unit/image/downloading.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/unit/image/downloading.js b/test/unit/image/downloading.js index 67419dfe02..fa3df09dd1 100644 --- a/test/unit/image/downloading.js +++ b/test/unit/image/downloading.js @@ -340,9 +340,36 @@ suite('p5.prototype.saveGif', function() { assert.ok(myp5.saveGif); assert.typeOf(myp5.saveGif, 'function'); }); + test('should not throw an error', function() { - myp5.saveGif('myGif', 3, 2); + myp5.saveGif('myGif', 3); + }); + + test('should not throw an error', function() { + myp5.saveGif('myGif', 3, { delay: 2, frames: 'seconds' }); + }); + + test('wrong parameter type #0', function(done) { + assert.validationError(function() { + myp5.saveGif(2, 2); + done(); + }); + }); + + test('wrong parameter type #1', function(done) { + assert.validationError(function() { + myp5.saveGif('mySketch', '2'); + done(); + }); + }); + + test('wrong parameter type #2', function(done) { + assert.validationError(function() { + myp5.saveGif('mySketch', 2, 'delay'); + done(); + }); }); + testWithDownload('should download a GIF', async function(blobContainer) { myp5.saveGif(myGif, 3, 2); await waitForBlob(blobContainer); From c8baf6e3f036ecf89ed6879d608fe91a7808d6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Sun, 21 Aug 2022 11:08:12 +0200 Subject: [PATCH 54/61] addressing mentor's feedback --- src/image/loading_displaying.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 5995ec5f64..8fb0a891cc 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -248,12 +248,10 @@ p5.prototype.saveGif = async function( // that duration translates to const nFrames = units === 'seconds' ? duration * _frameRate : duration; const nFramesDelay = units === 'seconds' ? delay * _frameRate : delay; + const totalNumberOfFrames = nFrames + nFramesDelay; // initialize variables for the frames processing - let count = nFramesDelay; - - // we start on the frame set by the delay argument - // frameCount = nFramesDelay; + let frameIterator = nFramesDelay; const lastPixelDensity = this._pixelDensity; this.pixelDensity(1); @@ -261,8 +259,9 @@ p5.prototype.saveGif = async function( // We first take every frame that we are going to use for the animation let frames = []; - if (document.getElementById('progressBar') !== null) - document.getElementById('progressBar').remove(); + let progressBarIdName = 'p5.gif.progressBar'; + if (document.getElementById(progressBarIdName) !== null) + document.getElementById(progressBarIdName).remove(); let p = this.createP(''); p.id('progressBar'); @@ -286,13 +285,13 @@ p5.prototype.saveGif = async function( // stop the loop since we are going to manually redraw this.noLoop(); - while (count < nFrames + nFramesDelay) { + while (frameIterator < totalNumberOfFrames) { /* we draw the next frame. this is important, since busy sketches or low end devices might take longer to render some frames. So we just wait for the frame to be drawn and immediately save it to a buffer and continue - */ + */ this.redraw(); // depending on the context we'll extract the pixels one way @@ -320,7 +319,7 @@ p5.prototype.saveGif = async function( } frames.push(data); - count++; + frameIterator++; p.html( 'Saved frame ' + From d9c5fe0a022a130c5742ca68d4ed5aa23b2ebc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 22 Aug 2022 21:51:09 +0200 Subject: [PATCH 55/61] sanity and type checking for the arguments in options object --- src/image/loading_displaying.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8fb0a891cc..06a66bf1b7 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -225,6 +225,17 @@ p5.prototype.saveGif = async function( // validate parameters to throw friendly error p5._validateParameters('saveGif', arguments); + let optionsArg = arguments[arguments.length - 1]; + + // if arguments in the options object are not correct, cancel operation + if (typeof optionsArg.delay !== 'number') { + console.log(optionsArg.delay, typeof optionsArg.delay); + throw TypeError('Delay parameter must be a number'); + } + if (optionsArg.units !== 'seconds' || optionsArg.units !== 'frames') { + throw TypeError('Units parameter must be either "frames" or "seconds"'); + } + // get the project's framerate let _frameRate = this._targetFrameRate; // if it is undefined or some non useful value, assume it's 60 From 7be6a621c42290e97998bd52ff2d16c1dd7e364f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 22 Aug 2022 22:54:31 +0200 Subject: [PATCH 56/61] better validation of arguments --- src/image/loading_displaying.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 06a66bf1b7..73c1141f57 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -220,22 +220,30 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { p5.prototype.saveGif = async function( fileName, duration, - { delay = 0, units = 'seconds' } + options = { delay: 0, units: 'seconds' } ) { - // validate parameters to throw friendly error - p5._validateParameters('saveGif', arguments); - - let optionsArg = arguments[arguments.length - 1]; - + // validate parameters + if (typeof fileName !== 'string') { + throw TypeError('fileName parameter must be a string'); + } + if (typeof duration !== 'number') { + throw TypeError('Duration parameter must be a number'); + } // if arguments in the options object are not correct, cancel operation - if (typeof optionsArg.delay !== 'number') { - console.log(optionsArg.delay, typeof optionsArg.delay); + if (typeof options.delay !== 'number') { throw TypeError('Delay parameter must be a number'); } - if (optionsArg.units !== 'seconds' || optionsArg.units !== 'frames') { + // if units is not seconds nor frames, throw error + if (options.units !== 'seconds' && options.units !== 'frames') { throw TypeError('Units parameter must be either "frames" or "seconds"'); } + // extract variables for more comfortable use + let units = options.units; + let delay = options.delay; + + // console.log(options); + // get the project's framerate let _frameRate = this._targetFrameRate; // if it is undefined or some non useful value, assume it's 60 From 928ee37d3f365196b06aa3d70167f799b3592cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 22 Aug 2022 23:52:15 +0200 Subject: [PATCH 57/61] improve example and add frameCount support --- src/image/loading_displaying.js | 56 ++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 73c1141f57..c2beed7135 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -167,49 +167,54 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * on the value of units. More on that on the next paragraph. * * An optional object that can contain two more arguments: delay (number) and units (string). - * + * * `delay`, specifying how much time we should wait before recording - * - * `units`, a string that can be either 'seconds' or 'frames'. By default it's 'seconds'. - * + * + * `units`, a string that can be either 'seconds' or 'frames'. By default it's 'seconds'. + * * `units` specifies how the duration and delay arguments will behave. - * If 'seconds', these arguments will correspond to seconds, meaning that 3 seconds worth of animation + * If 'seconds', these arguments will correspond to seconds, meaning that 3 seconds worth of animation * will be created. If 'frames', the arguments now correspond to the number of frames you want your * animation to be, if you are very sure of this number. * * @method saveGif * @param {String} filename File name of your gif * @param {Number} duration Duration in seconds that you wish to capture from your sketch - * @param {Object} options An optional object that can contain two more arguments: delay, specifying - * how much time we should wait before recording, and units, a string that can be either 'seconds' or - * 'frames'. By default it's 'seconds'. + * @param {Object} options An optional object that can contain two more arguments: delay, specifying + * how much time we should wait before recording, and units, a string that can be either 'seconds' or + * 'frames'. By default it's 'seconds'. * * @example *
* * function setup() { - * createCanvas(100, 100); - * colorMode(HSL); + * createCanvas(100,100); * } - + * * function draw() { - * // create some cool dynamic background - * let hue = map(sin(frameCount / 100), -1, 1, 0, 100); - * background(hue, 40, 60); - - * // create a circle that moves diagonally - * circle( - * 100 * sin(frameCount / 10) + width / 2, - * 100 * sin(frameCount / 10) + height / 2, - * 10 - * ); + * colorMode(RGB); + * background(30); + * + * // create a bunch of circles that move in... circles! + * for (let i = 0; i < 10; i++) { + * let opacity = map(i, 0, 10, 0, 255); + * noStroke(); + * fill(230, 250, 90, opacity); + * circle( + * 30 * sin(frameCount / (30 - i)) + width / 2, + * 30 * cos(frameCount / (30 - i)) + height / 2, + * 10 + * ); + * } * } - + * * // you can put it in the mousePressed function, * // or keyPressed for example - * function mousePressed() { - * // this will download the first two seconds of my animation! - * saveGif('mySketch', 2, { units: 'seconds', delay: 0 }); + * function keyPressed() { + * // this will download the first 5 seconds of the animation! + * if (key === 's') { + * saveGif('mySketch', 5); + * } * } * *
@@ -271,6 +276,7 @@ p5.prototype.saveGif = async function( // initialize variables for the frames processing let frameIterator = nFramesDelay; + frameCount = frameIterator; const lastPixelDensity = this._pixelDensity; this.pixelDensity(1); From 64c4d7afc1dea3ceb35997e5e10851c03713222b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Mon, 22 Aug 2022 23:52:32 +0200 Subject: [PATCH 58/61] fix linter error in example --- src/image/loading_displaying.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index c2beed7135..ea4b23af0c 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -188,7 +188,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { *
* * function setup() { - * createCanvas(100,100); + * createCanvas(100, 100); * } * * function draw() { From 8931fb1703ed6493567967c1215e0e0afff23391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Tue, 23 Aug 2022 00:06:46 +0200 Subject: [PATCH 59/61] put sketch back to normal --- lib/empty-example/sketch.js | 265 +----------------------------------- 1 file changed, 2 insertions(+), 263 deletions(-) diff --git a/lib/empty-example/sketch.js b/lib/empty-example/sketch.js index d9e79ed6b5..69b202ee71 100644 --- a/lib/empty-example/sketch.js +++ b/lib/empty-example/sketch.js @@ -1,268 +1,7 @@ -/* eslint-disable no-unused-vars */ - -// let saving = false; -// function setup() { -// // put setup code here -// createCanvas(500, 500); -// } - -// function draw() { -// // put drawing code here -// let hue = map(sin(frameCount), -1, 1, 127, 255); -// let hue_2 = map(sin(frameCount / 100) + 0.791, -1, 1, 127, 255); - -// strokeWeight(0); -// line(width / 2, 0, width / 2, height); -// line(0, height / 2, width, height / 2); - -// fill(250, 250, 20); -// rect(0, 0, width / 2, height / 2); - -// // fill(80, 80, hue); -// rect(width / 2, 0, width / 2, height / 2); - -// fill(20, 250, 250); -// rect(0, height / 2, width / 2, height / 2); - -// // fill(240, 240, 0); -// rect(width / 2, height / 2, width / 2, height / 2); - -// fill(30); -// stroke(20, 250, 20); -// strokeWeight(4); -// circle( -// 100 * sin(frameCount / 20) + width / 2, -// // 100 * sin(frameCount / 20) + height / 2, -// // width / 2, -// height / 2, -// 100 -// ); - -// if (saving) { -// save('frame' + frameCount.toString()); -// } -// } - -// function mousePressed() { -// if (mouseButton === RIGHT) { -// saveGif('mySketch', 1, 3); -// } -// } - -// function keyPressed() { -// switch (key) { -// case 's': -// frameRate(3); -// frameCount = 0; -// saving = !saving; - -// if (!saving) frameRate(60); -// break; -// } -// } - -// / COMPLEX SKETCH -let offset; -let spacing; - function setup() { - // randomSeed(1312); - - w = min(windowHeight, windowWidth); - createCanvas(w, w); - print(w); - looping = false; - saving = false; - noLoop(); - - divisor = random(1.2, 3).toFixed(2); - - frameWidth = w / divisor; - offset = (-frameWidth + w) / 2; - - gen_num_total_squares = int(random(2, 20)); - spacing = frameWidth / gen_num_total_squares; - - initHue = random(0, 360); - compColor = (initHue + 360 / random(1, 4)) % 360; - - gen_stroke_weight = random(-100, 100); - gen_stroke_fade_speed = random(30, 150); - gen_shift_small_squares = random(0, 10); - - gen_offset_small_sq_i = random(3, 10); - gen_offset_small_sq_j = random(3, 10); - - gen_rotation_speed = random(30, 250); - - gen_depth = random(5, 20); - gen_offset_i = random(1, 10); - gen_offset_j = random(1, 10); - - gen_transparency = random(20, 255); - - background(24); - // saveGif('mySketch', 2); + // put setup code here } function draw() { - colorMode(HSB); - background(initHue, 80, 20, gen_transparency); - makeSquares(); - // addHandle(); - - if (saving) save('grid' + frameCount + '.png'); -} - -function makeSquares(depth = gen_depth) { - colorMode(HSB); - let count_i = 0; - - for (let i = offset; i < w - offset; i += spacing) { - let count_j = 0; - count_i++; - - if (count_i > gen_num_total_squares) break; - - for (let j = offset; j < w - offset; j += spacing) { - count_j++; - - if (count_j > gen_num_total_squares) break; - - for (let n = 0; n < depth; n++) { - noFill(); - - if (n === 0) { - stroke(initHue, 100, 100); - fill( - initHue, - 100, - 100, - map( - sin( - gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed - ), - -1, - 1, - 0, - 0.3 - ) - ); - } else { - stroke(compColor, map(n, 0, depth, 100, 0), 100); - fill( - compColor, - 100, - 100, - map( - cos( - gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed - ), - -1, - 1, - 0, - 0.3 - ) - ); - } - - strokeWeight( - map( - sin( - gen_stroke_weight * (i + j) + frameCount / gen_stroke_fade_speed - ), - -1, - 1, - 0, - 1.5 - ) - ); - - push(); - translate(i + spacing / 2, j + spacing / 2); - - rotate( - i * gen_offset_i + - j * gen_offset_j + - frameCount / (gen_rotation_speed / (n + 1)) - ); - - if (n % 2 !== 0) { - translate( - sin(frameCount / 50) * gen_shift_small_squares, - cos(frameCount / 50) * gen_shift_small_squares - ); - rotate(i * gen_offset_i + j * gen_offset_j + frameCount / 100); - } - - if (n > 0) - rect( - -spacing / (gen_offset_small_sq_i + n), - -spacing / (gen_offset_small_sq_j + n), - spacing / (n + 1), - spacing / (n + 1) - ); - else rect(-spacing / 2, -spacing / 2, spacing, spacing); - - pop(); - } - // strokeWeight(40); - // point(i, j); - } - } -} - -function addHandle() { - fill(40); - noStroke(); - textAlign(RIGHT, BOTTOM); - textFont(font); - textSize(20); - text('@jesi_rgb', w - 30, w - 30); -} - -function mousePressed() { - if (mouseButton === LEFT) { - if (looping) { - noLoop(); - looping = false; - } else { - loop(); - looping = true; - } - } -} - -function keyPressed() { - console.log(key); - switch (key) { - // pressing the 's' key - case 's': - saveGif('mySketch', 2); - break; - - // pressing the '0' key - case '0': - frameCount = 0; - loop(); - noLoop(); - break; - - // pressing the ← key - case 'ArrowLeft': - frameCount >= 0 ? (frameCount -= 1) : (frameCount = 0); - noLoop(); - console.log(frameCount); - break; - - // pressing the → key - case 'ArrowRights': - frameCount += 1; - noLoop(); - console.log(frameCount); - break; - - default: - break; - } + // put drawing code here } From 79fd7c15ff239e926310af1667647d17e2d51b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Wed, 31 Aug 2022 09:02:11 +0200 Subject: [PATCH 60/61] frameCount was missing this dot. also corrected alt text --- lib/empty-example/.gitignore | 2 ++ src/image/loading_displaying.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 lib/empty-example/.gitignore diff --git a/lib/empty-example/.gitignore b/lib/empty-example/.gitignore new file mode 100644 index 0000000000..dcaab9e390 --- /dev/null +++ b/lib/empty-example/.gitignore @@ -0,0 +1,2 @@ +*.html +*.js \ No newline at end of file diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index ea4b23af0c..f3f6352715 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -220,7 +220,7 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { *
* * @alt - * animation of a circle moving smoothly diagonally + * animation of a group of yellow circles moving in circles over a dark background */ p5.prototype.saveGif = async function( fileName, @@ -276,7 +276,7 @@ p5.prototype.saveGif = async function( // initialize variables for the frames processing let frameIterator = nFramesDelay; - frameCount = frameIterator; + this.frameCount = frameIterator; const lastPixelDensity = this._pixelDensity; this.pixelDensity(1); From ac2964219b36f1fd832e0c47064134ce211e3da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Enrique=20Rasc=C3=B3n?= Date: Fri, 2 Sep 2022 13:14:25 +0200 Subject: [PATCH 61/61] update documentation for warning on function use --- src/image/loading_displaying.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index f3f6352715..eb125d8309 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -177,6 +177,10 @@ p5.prototype.loadImage = function(path, successCallback, failureCallback) { * will be created. If 'frames', the arguments now correspond to the number of frames you want your * animation to be, if you are very sure of this number. * + * It is not recommended to write this function inside setup, since it won't work properly. + * The recommended use can be seen in the example, where we use it inside an event function, + * like keyPressed or mousePressed. + * * @method saveGif * @param {String} filename File name of your gif * @param {Number} duration Duration in seconds that you wish to capture from your sketch