diff --git a/src/accessibility/gridOutput.js b/src/accessibility/gridOutput.js
index 8d8546faf1..e0a62ddbef 100644
--- a/src/accessibility/gridOutput.js
+++ b/src/accessibility/gridOutput.js
@@ -49,10 +49,7 @@ function _gridMap(idT, ingredients) {
let shapeNumber = 0;
let table = '';
//create an array of arrays 10*10 of empty cells
- let cells = Array.apply(null, Array(10)).map(function() {});
- for (let r in cells) {
- cells[r] = Array.apply(null, Array(10)).map(function() {});
- }
+ let cells = Array.from(Array(10), () => Array(10));
for (let x in ingredients) {
for (let y in ingredients[x]) {
let fill;
diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js
index 5f9114a071..9c9ce88782 100644
--- a/src/core/friendly_errors/fes_core.js
+++ b/src/core/friendly_errors/fes_core.js
@@ -399,7 +399,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
msg = translator('fes.misspelling', {
name: errSym,
- suggestions: suggestions,
+ suggestions,
location: locationObj ? translator('fes.location', locationObj) : '',
count: matchedSymbols.length
});
@@ -443,7 +443,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
translationObj = {
func: frame.functionName,
line: frame.lineNumber,
- location: location,
+ location,
file: frame.fileName.split('/').slice(-1)
};
if (idx === 0) {
@@ -568,7 +568,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
) {
p5._friendlyError(
translator('fes.wrongPreload', {
- func: func,
+ func,
location: locationObj
? translator('fes.location', locationObj)
: '',
@@ -580,7 +580,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
// Library error
p5._friendlyError(
translator('fes.libraryError', {
- func: func,
+ func,
location: locationObj
? translator('fes.location', locationObj)
: '',
diff --git a/src/core/friendly_errors/sketch_reader.js b/src/core/friendly_errors/sketch_reader.js
index d7778dd801..eac502f51f 100644
--- a/src/core/friendly_errors/sketch_reader.js
+++ b/src/core/friendly_errors/sketch_reader.js
@@ -71,7 +71,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
//display the FES message if a match is found
p5._friendlyError(
translator('fes.sketchReaderErrors.reservedConst', {
- url: url,
+ url,
symbol: variableArray[i]
})
);
@@ -103,7 +103,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
let url = `https://p5js.org/reference/#/p5/${variableArray[i]}`;
p5._friendlyError(
translator('fes.sketchReaderErrors.reservedFunc', {
- url: url,
+ url,
symbol: variableArray[i]
})
);
@@ -299,7 +299,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
let url = `https://p5js.org/reference/#/p5/${tempArray[i]}`;
p5._friendlyError(
translator('fes.sketchReaderErrors.reservedConst', {
- url: url,
+ url,
symbol: tempArray[i]
})
);
@@ -357,7 +357,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
let url = `https://p5js.org/reference/#/p5/${functionArray[i]}`;
p5._friendlyError(
translator('fes.sketchReaderErrors.reservedFunc', {
- url: url,
+ url,
symbol: functionArray[i]
})
);
diff --git a/src/core/friendly_errors/stacktrace.js b/src/core/friendly_errors/stacktrace.js
index 8604b3b801..fe6a1b5722 100644
--- a/src/core/friendly_errors/stacktrace.js
+++ b/src/core/friendly_errors/stacktrace.js
@@ -24,8 +24,6 @@ import p5 from '../main';
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
function ErrorStackParser() {
- 'use strict';
-
let FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/;
let CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
let SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/;
@@ -101,8 +99,8 @@ function ErrorStackParser() {
: locationParts[0];
return {
- functionName: functionName,
- fileName: fileName,
+ functionName,
+ fileName,
lineNumber: locationParts[1],
columnNumber: locationParts[2],
source: line
@@ -138,7 +136,7 @@ function ErrorStackParser() {
);
return {
- functionName: functionName,
+ functionName,
fileName: locationParts[0],
lineNumber: locationParts[1],
columnNumber: locationParts[2],
@@ -228,8 +226,8 @@ function ErrorStackParser() {
: argsRaw.split(',');
return {
- functionName: functionName,
- args: args,
+ functionName,
+ args,
fileName: locationParts[0],
lineNumber: locationParts[1],
columnNumber: locationParts[2],
diff --git a/src/core/friendly_errors/validate_params.js b/src/core/friendly_errors/validate_params.js
index a8b320d062..0998ae9650 100644
--- a/src/core/friendly_errors/validate_params.js
+++ b/src/core/friendly_errors/validate_params.js
@@ -633,7 +633,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
}`;
translationObj.location = translator('fes.location', {
- location: location,
+ location,
// for e.g. get "sketch.js" from "https://example.com/abc/sketch.js"
file: parsed[3].fileName.split('/').slice(-1),
line: parsed[3].lineNumber
diff --git a/src/core/p5.Graphics.js b/src/core/p5.Graphics.js
index 316e2cd974..8d7c00eb74 100644
--- a/src/core/p5.Graphics.js
+++ b/src/core/p5.Graphics.js
@@ -23,7 +23,7 @@ import * as constants from './constants';
* @param {p5} [pInst] pointer to p5 instance
*/
p5.Graphics = class extends p5.Element {
- constructor (w, h, renderer, pInst) {
+ constructor(w, h, renderer, pInst) {
let canvas = document.createElement('canvas');
super(canvas, pInst);
const r = renderer || constants.P2D;
@@ -56,7 +56,7 @@ p5.Graphics = class extends p5.Element {
pInst._elements.push(this);
Object.defineProperty(this, 'deltaTime', {
- get: function() {
+ get() {
return this._pInst.deltaTime;
}
});
@@ -184,19 +184,20 @@ p5.Graphics = class extends p5.Element {
this.elt.removeEventListener(elt_ev, this._events[elt_ev]);
}
}
-};
-/**
- * Creates and returns a new p5.Framebuffer
- * inside a p5.Graphics WebGL context.
- *
- * This takes the same parameters as the global
- * createFramebuffer function.
- *
- * @method createFramebuffer
- */
-p5.Graphics.prototype.createFramebuffer = function(options) {
- return new p5.Framebuffer(this, options);
+
+ /**
+ * Creates and returns a new p5.Framebuffer
+ * inside a p5.Graphics WebGL context.
+ *
+ * This takes the same parameters as the global
+ * createFramebuffer function.
+ *
+ * @method createFramebuffer
+ */
+ createFramebuffer(options) {
+ return new p5.Framebuffer(this, options);
+ }
};
export default p5.Graphics;
diff --git a/src/core/preload.js b/src/core/preload.js
index b4c8310ef5..f7155d361c 100644
--- a/src/core/preload.js
+++ b/src/core/preload.js
@@ -1,5 +1,3 @@
-'use strict';
-
import p5 from './main';
p5.prototype._promisePreloads = [
@@ -117,13 +115,13 @@ p5.prototype._legacyPreloadGenerator = function(
// of a specific class.
const baseValueGenerator =
legacyPreloadSetup.createBaseObject || objectCreator;
- let returnedFunction = function() {
+ let returnedFunction = function(...args) {
// Our then clause needs to run before setup, so we also increment the preload counter
this._incrementPreload();
// Generate the return value based on the generator.
- const returnValue = baseValueGenerator.apply(this, arguments);
+ const returnValue = baseValueGenerator.apply(this, args);
// Run the original wrapper
- fn.apply(this, arguments).then(data => {
+ fn.apply(this, args).then(data => {
// Copy each key from the resolved value into returnValue
Object.assign(returnValue, data);
// Decrement the preload counter, to allow setup to continue.
diff --git a/src/core/shape/2d_primitives.js b/src/core/shape/2d_primitives.js
index d10a55ad32..979b0adb4b 100644
--- a/src/core/shape/2d_primitives.js
+++ b/src/core/shape/2d_primitives.js
@@ -271,7 +271,7 @@ p5.prototype.arc = function(x, y, w, h, start, stop, mode, detail) {
*/
p5.prototype.ellipse = function(x, y, w, h, detailX) {
p5._validateParameters('ellipse', arguments);
- return this._renderEllipse.apply(this, arguments);
+ return this._renderEllipse(...arguments);
};
/**
@@ -304,7 +304,7 @@ p5.prototype.circle = function() {
const args = Array.prototype.slice.call(arguments, 0, 2);
args.push(arguments[2]);
args.push(arguments[2]);
- return this._renderEllipse.apply(this, args);
+ return this._renderEllipse(...args);
};
// internal method for drawing ellipses (without parameter validation)
@@ -615,9 +615,9 @@ p5.prototype.quad = function(...args) {
* @param {Integer} [detailY] number of segments in the y-direction (for WebGL mode)
* @chainable
*/
-p5.prototype.rect = function() {
- p5._validateParameters('rect', arguments);
- return this._renderRect.apply(this, arguments);
+p5.prototype.rect = function(...args) {
+ p5._validateParameters('rect', args);
+ return this._renderRect(...args);
};
/**
diff --git a/src/core/shim.js b/src/core/shim.js
index eb2c14ad53..8bf38fdf57 100644
--- a/src/core/shim.js
+++ b/src/core/shim.js
@@ -53,9 +53,9 @@ window.requestAnimationFrame = (() =>
typeof Symbol === 'function' && typeof Symbol() === 'symbol';
const propIsEnumerable = Object.prototype.propertyIsEnumerable;
const isEnumerableOn = obj =>
- function isEnumerable(prop) {
+ (function isEnumerable(prop) {
return propIsEnumerable.call(obj, prop);
- };
+ });
// per ES6 spec, this function has to have a length of 2
const assignShim = function assign(target, source1) {
@@ -68,9 +68,9 @@ window.requestAnimationFrame = (() =>
source = Object(arguments[s]);
props = keys(source);
if (hasSymbols && Object.getOwnPropertySymbols) {
- props.push.apply(
- props,
- Object.getOwnPropertySymbols(source).filter(isEnumerableOn(source))
+ props.push(
+ ...Object.getOwnPropertySymbols(source)
+ .filter(isEnumerableOn(source))
);
}
for (i = 0; i < props.length; ++i) {
diff --git a/src/core/transform.js b/src/core/transform.js
index 2f56e2ddd2..dd4580ffe7 100644
--- a/src/core/transform.js
+++ b/src/core/transform.js
@@ -184,12 +184,12 @@ import p5 from './main';
* @param {Number} p numbers which define the 4x4 matrix to be multiplied
* @chainable
*/
-p5.prototype.applyMatrix = function() {
- let isTypedArray = arguments[0] instanceof Object.getPrototypeOf(Uint8Array);
- if (Array.isArray(arguments[0]) || isTypedArray) {
- this._renderer.applyMatrix(...arguments[0]);
+p5.prototype.applyMatrix = function(...args) {
+ let isTypedArray = args[0] instanceof Object.getPrototypeOf(Uint8Array);
+ if (Array.isArray(args[0]) || isTypedArray) {
+ this._renderer.applyMatrix(...args[0]);
} else {
- this._renderer.applyMatrix(...arguments);
+ this._renderer.applyMatrix(...args);
}
return this;
};
diff --git a/src/dom/dom.js b/src/dom/dom.js
index 9d28420c9f..043a39c3ad 100644
--- a/src/dom/dom.js
+++ b/src/dom/dom.js
@@ -146,10 +146,10 @@ p5.prototype._wrapElement = function (elt) {
const children = Array.prototype.slice.call(elt.children);
if (elt.tagName === 'INPUT' && elt.type === 'checkbox') {
let converted = new p5.Element(elt, this);
- converted.checked = function () {
- if (arguments.length === 0) {
+ converted.checked = function(...args) {
+ if (args.length === 0) {
return this.elt.checked;
- } else if (arguments[0]) {
+ } else if (args[0]) {
this.elt.checked = true;
} else {
this.elt.checked = false;
@@ -565,8 +565,8 @@ p5.prototype.createButton = function (label, value) {
* }
*
*/
-p5.prototype.createCheckbox = function () {
- p5._validateParameters('createCheckbox', arguments);
+p5.prototype.createCheckbox = function(...args) {
+ p5._validateParameters('createCheckbox', args);
// Create a container element
const elt = document.createElement('div');
@@ -585,12 +585,12 @@ p5.prototype.createCheckbox = function () {
//checkbox must be wrapped in p5.Element before label so that label appears after
const self = addElement(elt, this);
- self.checked = function () {
+ self.checked = function(...args) {
const cb = self.elt.firstElementChild.getElementsByTagName('input')[0];
if (cb) {
- if (arguments.length === 0) {
+ if (args.length === 0) {
return cb.checked;
- } else if (arguments[0]) {
+ } else if (args[0]) {
cb.checked = true;
} else {
cb.checked = false;
@@ -605,15 +605,15 @@ p5.prototype.createCheckbox = function () {
};
// Set the span element innerHTML as the label value if passed
- if (arguments[0]) {
- self.value(arguments[0]);
+ if (args[0]) {
+ self.value(args[0]);
const span = document.createElement('span');
- span.innerHTML = arguments[0];
+ span.innerHTML = args[0];
label.appendChild(span);
}
// Set the checked value of checkbox if passed
- if (arguments[1]) {
+ if (args[1]) {
checkbox.checked = true;
}
@@ -697,10 +697,10 @@ p5.prototype.createCheckbox = function () {
* @return {p5.Element}
*/
-p5.prototype.createSelect = function () {
- p5._validateParameters('createSelect', arguments);
+p5.prototype.createSelect = function(...args) {
+ p5._validateParameters('createSelect', args);
let self;
- let arg = arguments[0];
+ let arg = args[0];
if (arg instanceof p5.Element && arg.elt instanceof HTMLSelectElement) {
// If given argument is p5.Element of select type
self = arg;
@@ -877,7 +877,7 @@ p5.prototype.createSelect = function () {
* @method createRadio
* @return {p5.Element} pointer to p5.Element holding created node
*/
-p5.prototype.createRadio = function () {
+p5.prototype.createRadio = function(...args) {
// Creates a div, adds each option as an individual input inside it.
// If already given with a containerEl, will search for all input[radio]
// it, create a p5.Element out of it, add options to it and return the p5.Element.
@@ -885,7 +885,7 @@ p5.prototype.createRadio = function () {
let self;
let radioElement;
let name;
- const arg0 = arguments[0];
+ const arg0 = args[0];
if (
arg0 instanceof p5.Element &&
(arg0.elt instanceof HTMLDivElement || arg0.elt instanceof HTMLSpanElement)
@@ -901,7 +901,7 @@ p5.prototype.createRadio = function () {
self = addElement(arg0, this);
this.elt = arg0;
radioElement = arg0;
- if (typeof arguments[1] === 'string') name = arguments[1];
+ if (typeof args[1] === 'string') name = args[1];
} else {
if (typeof arg0 === 'string') name = arg0;
radioElement = document.createElement('div');
@@ -1472,8 +1472,8 @@ if (navigator.mediaDevices.getUserMedia === undefined) {
*
*
*/
-p5.prototype.createCapture = function () {
- p5._validateParameters('createCapture', arguments);
+p5.prototype.createCapture = function(...args) {
+ p5._validateParameters('createCapture', args);
// return if getUserMedia is not supported by browser
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
@@ -1484,7 +1484,7 @@ p5.prototype.createCapture = function () {
let useAudio = true;
let constraints;
let callback;
- for (const arg of arguments) {
+ for (const arg of args) {
if (arg === p5.prototype.VIDEO) useAudio = false;
else if (arg === p5.prototype.AUDIO) useVideo = false;
else if (typeof arg === 'object') constraints = arg;
@@ -1802,14 +1802,14 @@ p5.Element.prototype.center = function (align) {
* @param {boolean} [append] whether to append HTML to existing
* @chainable
*/
-p5.Element.prototype.html = function () {
- if (arguments.length === 0) {
+p5.Element.prototype.html = function(...args) {
+ if (args.length === 0) {
return this.elt.innerHTML;
- } else if (arguments[1]) {
- this.elt.insertAdjacentHTML('beforeend', arguments[0]);
+ } else if (args[1]) {
+ this.elt.insertAdjacentHTML('beforeend', args[0]);
return this;
} else {
- this.elt.innerHTML = arguments[0];
+ this.elt.innerHTML = args[0];
return this;
}
};
@@ -1854,32 +1854,32 @@ p5.Element.prototype.html = function () {
* @param {String} [positionType] it can be static, fixed, relative, sticky, initial or inherit (optional)
* @chainable
*/
-p5.Element.prototype.position = function () {
- if (arguments.length === 0) {
+p5.Element.prototype.position = function(...args) {
+ if (args.length === 0) {
return { x: this.elt.offsetLeft, y: this.elt.offsetTop };
} else {
let positionType = 'absolute';
if (
- arguments[2] === 'static' ||
- arguments[2] === 'fixed' ||
- arguments[2] === 'relative' ||
- arguments[2] === 'sticky' ||
- arguments[2] === 'initial' ||
- arguments[2] === 'inherit'
+ args[2] === 'static' ||
+ args[2] === 'fixed' ||
+ args[2] === 'relative' ||
+ args[2] === 'sticky' ||
+ args[2] === 'initial' ||
+ args[2] === 'inherit'
) {
- positionType = arguments[2];
+ positionType = args[2];
}
this.elt.style.position = positionType;
- this.elt.style.left = arguments[0] + 'px';
- this.elt.style.top = arguments[1] + 'px';
- this.x = arguments[0];
- this.y = arguments[1];
+ this.elt.style.left = args[0] + 'px';
+ this.elt.style.top = args[1] + 'px';
+ this.x = args[0];
+ this.y = args[1];
return this;
}
};
/* Helper method called by p5.Element.style() */
-p5.Element.prototype._translate = function () {
+p5.Element.prototype._translate = function(...args) {
this.elt.style.position = 'absolute';
// save out initial non-translate transform styling
let transform = '';
@@ -1887,22 +1887,22 @@ p5.Element.prototype._translate = function () {
transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
}
- if (arguments.length === 2) {
+ if (args.length === 2) {
this.elt.style.transform =
- 'translate(' + arguments[0] + 'px, ' + arguments[1] + 'px)';
- } else if (arguments.length > 2) {
+ 'translate(' + args[0] + 'px, ' + args[1] + 'px)';
+ } else if (args.length > 2) {
this.elt.style.transform =
'translate3d(' +
- arguments[0] +
+ args[0] +
'px,' +
- arguments[1] +
+ args[1] +
'px,' +
- arguments[2] +
+ args[2] +
'px)';
- if (arguments.length === 3) {
+ if (args.length === 3) {
this.elt.parentElement.style.perspective = '1000px';
} else {
- this.elt.parentElement.style.perspective = arguments[3] + 'px';
+ this.elt.parentElement.style.perspective = args[3] + 'px';
}
}
// add any extra transform styling back on end
@@ -1911,7 +1911,7 @@ p5.Element.prototype._translate = function () {
};
/* Helper method called by p5.Element.style() */
-p5.Element.prototype._rotate = function () {
+p5.Element.prototype._rotate = function(...args) {
// save out initial non-rotate transform styling
let transform = '';
if (this.elt.style.transform) {
@@ -1919,15 +1919,15 @@ p5.Element.prototype._rotate = function () {
transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
}
- if (arguments.length === 1) {
- this.elt.style.transform = 'rotate(' + arguments[0] + 'deg)';
- } else if (arguments.length === 2) {
+ if (args.length === 1) {
+ this.elt.style.transform = 'rotate(' + args[0] + 'deg)';
+ } else if (args.length === 2) {
this.elt.style.transform =
- 'rotate(' + arguments[0] + 'deg, ' + arguments[1] + 'deg)';
- } else if (arguments.length === 3) {
- this.elt.style.transform = 'rotateX(' + arguments[0] + 'deg)';
- this.elt.style.transform += 'rotateY(' + arguments[1] + 'deg)';
- this.elt.style.transform += 'rotateZ(' + arguments[2] + 'deg)';
+ 'rotate(' + args[0] + 'deg, ' + args[1] + 'deg)';
+ } else if (args.length === 3) {
+ this.elt.style.transform = 'rotateX(' + args[0] + 'deg)';
+ this.elt.style.transform += 'rotateY(' + args[1] + 'deg)';
+ this.elt.style.transform += 'rotateZ(' + args[2] + 'deg)';
}
// add remaining transform back on
this.elt.style.transform += transform;
@@ -2153,9 +2153,9 @@ p5.Element.prototype.removeAttribute = function (attr) {
* @param {String|Number} value
* @chainable
*/
-p5.Element.prototype.value = function () {
- if (arguments.length > 0) {
- this.elt.value = arguments[0];
+p5.Element.prototype.value = function(...args) {
+ if (args.length > 0) {
+ this.elt.value = args[0];
return this;
} else {
if (this.elt.type === 'range') {
@@ -2408,8 +2408,7 @@ p5.Element.prototype.drop = function (callback, fxn) {
const files = evt.dataTransfer.files;
// Load each one and trigger the callback
- for (let i = 0; i < files.length; i++) {
- const f = files[i];
+ for (const f of files) {
p5.File._load(f, callback);
}
},
@@ -3224,9 +3223,9 @@ p5.MediaElement = class MediaElement extends p5.Element {
this._frameOnCanvas = this._pInst.frameCount;
}
}
- loadPixels() {
+ loadPixels(...args) {
this._ensureCanvas();
- return p5.Renderer2D.prototype.loadPixels.apply(this, arguments);
+ return p5.Renderer2D.prototype.loadPixels.apply(this, args);
}
updatePixels(x, y, w, h) {
if (this.loadedmetadata) {
@@ -3237,13 +3236,13 @@ p5.MediaElement = class MediaElement extends p5.Element {
this.setModified(true);
return this;
}
- get() {
+ get(...args) {
this._ensureCanvas();
- return p5.Renderer2D.prototype.get.apply(this, arguments);
+ return p5.Renderer2D.prototype.get.apply(this, args);
}
- _getPixel() {
+ _getPixel(...args) {
this.loadPixels();
- return p5.Renderer2D.prototype._getPixel.apply(this, arguments);
+ return p5.Renderer2D.prototype._getPixel.apply(this, args);
}
set(x, y, imgOrCol) {
@@ -3254,14 +3253,14 @@ p5.MediaElement = class MediaElement extends p5.Element {
this.setModified(true);
}
}
- copy() {
+ copy(...args) {
this._ensureCanvas();
- p5.prototype.copy.apply(this, arguments);
+ p5.prototype.copy.apply(this, args);
}
- mask() {
+ mask(...args) {
this.loadPixels();
this.setModified(true);
- p5.Image.prototype.mask.apply(this, arguments);
+ p5.Image.prototype.mask.apply(this, args);
}
/**
* helper method for web GL mode to figure out if the element
diff --git a/src/image/filters.js b/src/image/filters.js
index 2dda4c7499..d4707438e3 100644
--- a/src/image/filters.js
+++ b/src/image/filters.js
@@ -13,49 +13,49 @@
* or the java processing implementation.
*/
-const Filters = {};
-
-/*
- * Helper functions
- */
-
-/**
- * Returns the pixel buffer for a canvas
- *
- * @private
- *
- * @param {Canvas|ImageData} canvas the canvas to get pixels from
- * @return {Uint8ClampedArray} a one-dimensional array containing
- * the data in thc RGBA order, with integer
- * values between 0 and 255
- */
-Filters._toPixels = function(canvas) {
- if (canvas instanceof ImageData) {
- return canvas.data;
- } else {
- if (canvas.getContext('2d')) {
- return canvas
- .getContext('2d')
- .getImageData(0, 0, canvas.width, canvas.height).data;
- } else if (canvas.getContext('webgl')) {
- const gl = canvas.getContext('webgl');
- const len = gl.drawingBufferWidth * gl.drawingBufferHeight * 4;
- const data = new Uint8Array(len);
- gl.readPixels(
- 0,
- 0,
- canvas.width,
- canvas.height,
- gl.RGBA,
- gl.UNSIGNED_BYTE,
- data
- );
- return data;
+const Filters = {
+
+ /*
+ * Helper functions
+ */
+
+ /**
+ * Returns the pixel buffer for a canvas
+ *
+ * @private
+ *
+ * @param {Canvas|ImageData} canvas the canvas to get pixels from
+ * @return {Uint8ClampedArray} a one-dimensional array containing
+ * the data in thc RGBA order, with integer
+ * values between 0 and 255
+ */
+ _toPixels(canvas) {
+ if (canvas instanceof ImageData) {
+ return canvas.data;
+ } else {
+ if (canvas.getContext('2d')) {
+ return canvas
+ .getContext('2d')
+ .getImageData(0, 0, canvas.width, canvas.height).data;
+ } else if (canvas.getContext('webgl')) {
+ const gl = canvas.getContext('webgl');
+ const len = gl.drawingBufferWidth * gl.drawingBufferHeight * 4;
+ const data = new Uint8Array(len);
+ gl.readPixels(
+ 0,
+ 0,
+ canvas.width,
+ canvas.height,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ data
+ );
+ return data;
+ }
}
- }
-};
+ },
-/**
+ /**
* Returns a 32 bit number containing ARGB data at ith pixel in the
* 1D array containing pixels data.
*
@@ -66,17 +66,17 @@ Filters._toPixels = function(canvas) {
* @return {Integer} 32 bit integer value representing
* ARGB value.
*/
-Filters._getARGB = function(data, i) {
- const offset = i * 4;
- return (
- ((data[offset + 3] << 24) & 0xff000000) |
- ((data[offset] << 16) & 0x00ff0000) |
- ((data[offset + 1] << 8) & 0x0000ff00) |
- (data[offset + 2] & 0x000000ff)
- );
-};
+ _getARGB(data, i) {
+ const offset = i * 4;
+ return (
+ ((data[offset + 3] << 24) & 0xff000000) |
+ ((data[offset] << 16) & 0x00ff0000) |
+ ((data[offset + 1] << 8) & 0x0000ff00) |
+ (data[offset + 2] & 0x000000ff)
+ );
+ },
-/**
+ /**
* Modifies pixels RGBA values to values contained in the data object.
*
* @private
@@ -85,18 +85,19 @@ Filters._getARGB = function(data, i) {
* @param {Int32Array} data source 1D array where each value
* represents ARGB values
*/
-Filters._setPixels = function(pixels, data) {
- let offset = 0;
- for (let i = 0, al = pixels.length; i < al; i++) {
- offset = i * 4;
- pixels[offset + 0] = (data[i] & 0x00ff0000) >>> 16;
- pixels[offset + 1] = (data[i] & 0x0000ff00) >>> 8;
- pixels[offset + 2] = data[i] & 0x000000ff;
- pixels[offset + 3] = (data[i] & 0xff000000) >>> 24;
- }
-};
+ _setPixels(pixels, data) {
+ let offset = 0;
+ for (let i = 0, al = pixels.length; i < al; i++) {
+ offset = i * 4;
+ pixels[offset + 0] = (data[i] & 0x00ff0000) >>> 16;
+ pixels[offset + 1] = (data[i] & 0x0000ff00) >>> 8;
+ pixels[offset + 2] = data[i] & 0x000000ff;
+ pixels[offset + 3] = (data[i] & 0xff000000) >>> 24;
+ }
+ },
-/**
+
+ /**
* Returns the ImageData object for a canvas
* https://developer.mozilla.org/en-US/docs/Web/API/ImageData
*
@@ -106,17 +107,18 @@ Filters._setPixels = function(pixels, data) {
* @return {ImageData} Holder of pixel data (and width and
* height) for a canvas
*/
-Filters._toImageData = function(canvas) {
- if (canvas instanceof ImageData) {
- return canvas;
- } else {
- return canvas
- .getContext('2d')
- .getImageData(0, 0, canvas.width, canvas.height);
- }
-};
+ _toImageData(canvas) {
+ if (canvas instanceof ImageData) {
+ return canvas;
+ } else {
+ return canvas
+ .getContext('2d')
+ .getImageData(0, 0, canvas.width, canvas.height);
+ }
+ },
-/**
+
+ /**
* Returns a blank ImageData object.
*
* @private
@@ -125,13 +127,13 @@ Filters._toImageData = function(canvas) {
* @param {Integer} height
* @return {ImageData}
*/
-Filters._createImageData = function(width, height) {
- Filters._tmpCanvas = document.createElement('canvas');
- Filters._tmpCtx = Filters._tmpCanvas.getContext('2d');
- return this._tmpCtx.createImageData(width, height);
-};
+ _createImageData(width, height) {
+ Filters._tmpCanvas = document.createElement('canvas');
+ Filters._tmpCtx = Filters._tmpCanvas.getContext('2d');
+ return this._tmpCtx.createImageData(width, height);
+ },
-/**
+ /**
* Applys a filter function to a canvas.
*
* The difference between this and the actual filter functions defined below
@@ -150,41 +152,42 @@ Filters._createImageData = function(width, height) {
* @param {function(ImageData,Object)} func [description]
* @param {Object} filterParam [description]
*/
-Filters.apply = function(canvas, func, filterParam) {
- const pixelsState = canvas.getContext('2d');
- const imageData = pixelsState.getImageData(0, 0, canvas.width, canvas.height);
-
- //Filters can either return a new ImageData object, or just modify
- //the one they received.
- const newImageData = func(imageData, filterParam);
- if (newImageData instanceof ImageData) {
- pixelsState.putImageData(
- newImageData,
- 0,
- 0,
- 0,
- 0,
- canvas.width,
- canvas.height
- );
- } else {
- pixelsState.putImageData(
- imageData,
- 0,
- 0,
- 0,
- 0,
- canvas.width,
- canvas.height
- );
- }
-};
+ apply(canvas, func, filterParam) {
+ const pixelsState = canvas.getContext('2d');
+ const imageData = pixelsState.getImageData(
+ 0, 0, canvas.width, canvas.height);
+
+ //Filters can either return a new ImageData object, or just modify
+ //the one they received.
+ const newImageData = func(imageData, filterParam);
+ if (newImageData instanceof ImageData) {
+ pixelsState.putImageData(
+ newImageData,
+ 0,
+ 0,
+ 0,
+ 0,
+ canvas.width,
+ canvas.height
+ );
+ } else {
+ pixelsState.putImageData(
+ imageData,
+ 0,
+ 0,
+ 0,
+ 0,
+ canvas.width,
+ canvas.height
+ );
+ }
+ },
-/*
+ /*
* Filters
*/
-/**
+ /**
* Converts the image to black and white pixels depending if they are above or
* below the threshold defined by the level parameter. The parameter must be
* between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is used.
@@ -195,30 +198,30 @@ Filters.apply = function(canvas, func, filterParam) {
* @param {Canvas} canvas
* @param {Float} level
*/
-Filters.threshold = function(canvas, level) {
- const pixels = Filters._toPixels(canvas);
+ threshold(canvas, level) {
+ const pixels = Filters._toPixels(canvas);
- if (level === undefined) {
- level = 0.5;
- }
- const thresh = Math.floor(level * 255);
-
- for (let i = 0; i < pixels.length; i += 4) {
- const r = pixels[i];
- const g = pixels[i + 1];
- const b = pixels[i + 2];
- const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
- let val;
- if (gray >= thresh) {
- val = 255;
- } else {
- val = 0;
+ if (level === undefined) {
+ level = 0.5;
}
- pixels[i] = pixels[i + 1] = pixels[i + 2] = val;
- }
-};
+ const thresh = Math.floor(level * 255);
+
+ for (let i = 0; i < pixels.length; i += 4) {
+ const r = pixels[i];
+ const g = pixels[i + 1];
+ const b = pixels[i + 2];
+ const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+ let val;
+ if (gray >= thresh) {
+ val = 255;
+ } else {
+ val = 0;
+ }
+ pixels[i] = pixels[i + 1] = pixels[i + 2] = val;
+ }
+ },
-/**
+ /**
* Converts any colors in the image to grayscale equivalents.
* No parameter is used.
*
@@ -227,52 +230,52 @@ Filters.threshold = function(canvas, level) {
* @private
* @param {Canvas} canvas
*/
-Filters.gray = function(canvas) {
- const pixels = Filters._toPixels(canvas);
+ gray(canvas) {
+ const pixels = Filters._toPixels(canvas);
- for (let i = 0; i < pixels.length; i += 4) {
- const r = pixels[i];
- const g = pixels[i + 1];
- const b = pixels[i + 2];
+ for (let i = 0; i < pixels.length; i += 4) {
+ const r = pixels[i];
+ const g = pixels[i + 1];
+ const b = pixels[i + 2];
- // CIE luminance for RGB
- const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
- pixels[i] = pixels[i + 1] = pixels[i + 2] = gray;
- }
-};
+ // CIE luminance for RGB
+ const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+ pixels[i] = pixels[i + 1] = pixels[i + 2] = gray;
+ }
+ },
-/**
+ /**
* Sets the alpha channel to entirely opaque. No parameter is used.
*
* @private
* @param {Canvas} canvas
*/
-Filters.opaque = function(canvas) {
- const pixels = Filters._toPixels(canvas);
+ opaque(canvas) {
+ const pixels = Filters._toPixels(canvas);
- for (let i = 0; i < pixels.length; i += 4) {
- pixels[i + 3] = 255;
- }
+ for (let i = 0; i < pixels.length; i += 4) {
+ pixels[i + 3] = 255;
+ }
- return pixels;
-};
+ return pixels;
+ },
-/**
+ /**
* Sets each pixel to its inverse value. No parameter is used.
* @private
* @param {Canvas} canvas
*/
-Filters.invert = function(canvas) {
- const pixels = Filters._toPixels(canvas);
+ invert(canvas) {
+ const pixels = Filters._toPixels(canvas);
- for (let i = 0; i < pixels.length; i += 4) {
- pixels[i] = 255 - pixels[i];
- pixels[i + 1] = 255 - pixels[i + 1];
- pixels[i + 2] = 255 - pixels[i + 2];
- }
-};
+ for (let i = 0; i < pixels.length; i += 4) {
+ pixels[i] = 255 - pixels[i];
+ pixels[i + 1] = 255 - pixels[i + 1];
+ pixels[i + 2] = 255 - pixels[i + 2];
+ }
+ },
-/**
+ /**
* Limits each channel of the image to the number of colors specified as
* the parameter. The parameter can be set to values between 2 and 255, but
* results are most noticeable in the lower ranges.
@@ -283,199 +286,204 @@ Filters.invert = function(canvas) {
* @param {Canvas} canvas
* @param {Integer} level
*/
-Filters.posterize = function(canvas, level) {
- const pixels = Filters._toPixels(canvas);
+ posterize(canvas, level) {
+ const pixels = Filters._toPixels(canvas);
- if (level < 2 || level > 255) {
- throw new Error(
- 'Level must be greater than 2 and less than 255 for posterize'
- );
- }
+ if (level < 2 || level > 255) {
+ throw new Error(
+ 'Level must be greater than 2 and less than 255 for posterize'
+ );
+ }
- const levels1 = level - 1;
- for (let i = 0; i < pixels.length; i += 4) {
- const rlevel = pixels[i];
- const glevel = pixels[i + 1];
- const blevel = pixels[i + 2];
+ const levels1 = level - 1;
+ for (let i = 0; i < pixels.length; i += 4) {
+ const rlevel = pixels[i];
+ const glevel = pixels[i + 1];
+ const blevel = pixels[i + 2];
- pixels[i] = ((rlevel * level) >> 8) * 255 / levels1;
- pixels[i + 1] = ((glevel * level) >> 8) * 255 / levels1;
- pixels[i + 2] = ((blevel * level) >> 8) * 255 / levels1;
- }
-};
+ pixels[i] = ((rlevel * level) >> 8) * 255 / levels1;
+ pixels[i + 1] = ((glevel * level) >> 8) * 255 / levels1;
+ pixels[i + 2] = ((blevel * level) >> 8) * 255 / levels1;
+ }
+ },
-/**
+ /**
* reduces the bright areas in an image
* @private
* @param {Canvas} canvas
*/
-Filters.dilate = function(canvas) {
- const pixels = Filters._toPixels(canvas);
- let currIdx = 0;
- const maxIdx = pixels.length ? pixels.length / 4 : 0;
- const out = new Int32Array(maxIdx);
- let currRowIdx, maxRowIdx, colOrig, colOut, currLum;
-
- let idxRight, idxLeft, idxUp, idxDown;
- let colRight, colLeft, colUp, colDown;
- let lumRight, lumLeft, lumUp, lumDown;
-
- while (currIdx < maxIdx) {
- currRowIdx = currIdx;
- maxRowIdx = currIdx + canvas.width;
- while (currIdx < maxRowIdx) {
- colOrig = colOut = Filters._getARGB(pixels, currIdx);
- idxLeft = currIdx - 1;
- idxRight = currIdx + 1;
- idxUp = currIdx - canvas.width;
- idxDown = currIdx + canvas.width;
-
- if (idxLeft < currRowIdx) {
- idxLeft = currIdx;
- }
- if (idxRight >= maxRowIdx) {
- idxRight = currIdx;
- }
- if (idxUp < 0) {
- idxUp = 0;
- }
- if (idxDown >= maxIdx) {
- idxDown = currIdx;
- }
- colUp = Filters._getARGB(pixels, idxUp);
- colLeft = Filters._getARGB(pixels, idxLeft);
- colDown = Filters._getARGB(pixels, idxDown);
- colRight = Filters._getARGB(pixels, idxRight);
-
- //compute luminance
- currLum =
- 77 * ((colOrig >> 16) & 0xff) +
- 151 * ((colOrig >> 8) & 0xff) +
- 28 * (colOrig & 0xff);
- lumLeft =
- 77 * ((colLeft >> 16) & 0xff) +
- 151 * ((colLeft >> 8) & 0xff) +
- 28 * (colLeft & 0xff);
- lumRight =
- 77 * ((colRight >> 16) & 0xff) +
- 151 * ((colRight >> 8) & 0xff) +
- 28 * (colRight & 0xff);
- lumUp =
- 77 * ((colUp >> 16) & 0xff) +
- 151 * ((colUp >> 8) & 0xff) +
- 28 * (colUp & 0xff);
- lumDown =
- 77 * ((colDown >> 16) & 0xff) +
- 151 * ((colDown >> 8) & 0xff) +
- 28 * (colDown & 0xff);
-
- if (lumLeft > currLum) {
- colOut = colLeft;
- currLum = lumLeft;
- }
- if (lumRight > currLum) {
- colOut = colRight;
- currLum = lumRight;
- }
- if (lumUp > currLum) {
- colOut = colUp;
- currLum = lumUp;
- }
- if (lumDown > currLum) {
- colOut = colDown;
- currLum = lumDown;
+ dilate(canvas) {
+ const pixels = Filters._toPixels(canvas);
+ let currIdx = 0;
+ const maxIdx = pixels.length ? pixels.length / 4 : 0;
+ const out = new Int32Array(maxIdx);
+ let currRowIdx, maxRowIdx, colOrig, colOut, currLum;
+
+ let idxRight, idxLeft, idxUp, idxDown;
+ let colRight, colLeft, colUp, colDown;
+ let lumRight, lumLeft, lumUp, lumDown;
+
+ while (currIdx < maxIdx) {
+ currRowIdx = currIdx;
+ maxRowIdx = currIdx + canvas.width;
+ while (currIdx < maxRowIdx) {
+ colOrig = colOut = Filters._getARGB(pixels, currIdx);
+ idxLeft = currIdx - 1;
+ idxRight = currIdx + 1;
+ idxUp = currIdx - canvas.width;
+ idxDown = currIdx + canvas.width;
+
+ if (idxLeft < currRowIdx) {
+ idxLeft = currIdx;
+ }
+ if (idxRight >= maxRowIdx) {
+ idxRight = currIdx;
+ }
+ if (idxUp < 0) {
+ idxUp = 0;
+ }
+ if (idxDown >= maxIdx) {
+ idxDown = currIdx;
+ }
+ colUp = Filters._getARGB(pixels, idxUp);
+ colLeft = Filters._getARGB(pixels, idxLeft);
+ colDown = Filters._getARGB(pixels, idxDown);
+ colRight = Filters._getARGB(pixels, idxRight);
+
+ //compute luminance
+ currLum =
+ 77 * ((colOrig >> 16) & 0xff) +
+ 151 * ((colOrig >> 8) & 0xff) +
+ 28 * (colOrig & 0xff);
+ lumLeft =
+ 77 * ((colLeft >> 16) & 0xff) +
+ 151 * ((colLeft >> 8) & 0xff) +
+ 28 * (colLeft & 0xff);
+ lumRight =
+ 77 * ((colRight >> 16) & 0xff) +
+ 151 * ((colRight >> 8) & 0xff) +
+ 28 * (colRight & 0xff);
+ lumUp =
+ 77 * ((colUp >> 16) & 0xff) +
+ 151 * ((colUp >> 8) & 0xff) +
+ 28 * (colUp & 0xff);
+ lumDown =
+ 77 * ((colDown >> 16) & 0xff) +
+ 151 * ((colDown >> 8) & 0xff) +
+ 28 * (colDown & 0xff);
+
+ if (lumLeft > currLum) {
+ colOut = colLeft;
+ currLum = lumLeft;
+ }
+ if (lumRight > currLum) {
+ colOut = colRight;
+ currLum = lumRight;
+ }
+ if (lumUp > currLum) {
+ colOut = colUp;
+ currLum = lumUp;
+ }
+ if (lumDown > currLum) {
+ colOut = colDown;
+ currLum = lumDown;
+ }
+ out[currIdx++] = colOut;
}
- out[currIdx++] = colOut;
}
- }
- Filters._setPixels(pixels, out);
-};
+ Filters._setPixels(pixels, out);
+ },
-/**
+ /**
* increases the bright areas in an image
* @private
* @param {Canvas} canvas
*/
-Filters.erode = function(canvas) {
- const pixels = Filters._toPixels(canvas);
- let currIdx = 0;
- const maxIdx = pixels.length ? pixels.length / 4 : 0;
- const out = new Int32Array(maxIdx);
- let currRowIdx, maxRowIdx, colOrig, colOut, currLum;
- let idxRight, idxLeft, idxUp, idxDown;
- let colRight, colLeft, colUp, colDown;
- let lumRight, lumLeft, lumUp, lumDown;
-
- while (currIdx < maxIdx) {
- currRowIdx = currIdx;
- maxRowIdx = currIdx + canvas.width;
- while (currIdx < maxRowIdx) {
- colOrig = colOut = Filters._getARGB(pixels, currIdx);
- idxLeft = currIdx - 1;
- idxRight = currIdx + 1;
- idxUp = currIdx - canvas.width;
- idxDown = currIdx + canvas.width;
-
- if (idxLeft < currRowIdx) {
- idxLeft = currIdx;
- }
- if (idxRight >= maxRowIdx) {
- idxRight = currIdx;
- }
- if (idxUp < 0) {
- idxUp = 0;
- }
- if (idxDown >= maxIdx) {
- idxDown = currIdx;
- }
- colUp = Filters._getARGB(pixels, idxUp);
- colLeft = Filters._getARGB(pixels, idxLeft);
- colDown = Filters._getARGB(pixels, idxDown);
- colRight = Filters._getARGB(pixels, idxRight);
-
- //compute luminance
- currLum =
- 77 * ((colOrig >> 16) & 0xff) +
- 151 * ((colOrig >> 8) & 0xff) +
- 28 * (colOrig & 0xff);
- lumLeft =
- 77 * ((colLeft >> 16) & 0xff) +
- 151 * ((colLeft >> 8) & 0xff) +
- 28 * (colLeft & 0xff);
- lumRight =
- 77 * ((colRight >> 16) & 0xff) +
- 151 * ((colRight >> 8) & 0xff) +
- 28 * (colRight & 0xff);
- lumUp =
- 77 * ((colUp >> 16) & 0xff) +
- 151 * ((colUp >> 8) & 0xff) +
- 28 * (colUp & 0xff);
- lumDown =
- 77 * ((colDown >> 16) & 0xff) +
- 151 * ((colDown >> 8) & 0xff) +
- 28 * (colDown & 0xff);
-
- if (lumLeft < currLum) {
- colOut = colLeft;
- currLum = lumLeft;
- }
- if (lumRight < currLum) {
- colOut = colRight;
- currLum = lumRight;
- }
- if (lumUp < currLum) {
- colOut = colUp;
- currLum = lumUp;
- }
- if (lumDown < currLum) {
- colOut = colDown;
- currLum = lumDown;
- }
+ erode(canvas) {
+ const pixels = Filters._toPixels(canvas);
+ let currIdx = 0;
+ const maxIdx = pixels.length ? pixels.length / 4 : 0;
+ const out = new Int32Array(maxIdx);
+ let currRowIdx, maxRowIdx, colOrig, colOut, currLum;
+ let idxRight, idxLeft, idxUp, idxDown;
+ let colRight, colLeft, colUp, colDown;
+ let lumRight, lumLeft, lumUp, lumDown;
+
+ while (currIdx < maxIdx) {
+ currRowIdx = currIdx;
+ maxRowIdx = currIdx + canvas.width;
+ while (currIdx < maxRowIdx) {
+ colOrig = colOut = Filters._getARGB(pixels, currIdx);
+ idxLeft = currIdx - 1;
+ idxRight = currIdx + 1;
+ idxUp = currIdx - canvas.width;
+ idxDown = currIdx + canvas.width;
+
+ if (idxLeft < currRowIdx) {
+ idxLeft = currIdx;
+ }
+ if (idxRight >= maxRowIdx) {
+ idxRight = currIdx;
+ }
+ if (idxUp < 0) {
+ idxUp = 0;
+ }
+ if (idxDown >= maxIdx) {
+ idxDown = currIdx;
+ }
+ colUp = Filters._getARGB(pixels, idxUp);
+ colLeft = Filters._getARGB(pixels, idxLeft);
+ colDown = Filters._getARGB(pixels, idxDown);
+ colRight = Filters._getARGB(pixels, idxRight);
+
+ //compute luminance
+ currLum =
+ 77 * ((colOrig >> 16) & 0xff) +
+ 151 * ((colOrig >> 8) & 0xff) +
+ 28 * (colOrig & 0xff);
+ lumLeft =
+ 77 * ((colLeft >> 16) & 0xff) +
+ 151 * ((colLeft >> 8) & 0xff) +
+ 28 * (colLeft & 0xff);
+ lumRight =
+ 77 * ((colRight >> 16) & 0xff) +
+ 151 * ((colRight >> 8) & 0xff) +
+ 28 * (colRight & 0xff);
+ lumUp =
+ 77 * ((colUp >> 16) & 0xff) +
+ 151 * ((colUp >> 8) & 0xff) +
+ 28 * (colUp & 0xff);
+ lumDown =
+ 77 * ((colDown >> 16) & 0xff) +
+ 151 * ((colDown >> 8) & 0xff) +
+ 28 * (colDown & 0xff);
+
+ if (lumLeft < currLum) {
+ colOut = colLeft;
+ currLum = lumLeft;
+ }
+ if (lumRight < currLum) {
+ colOut = colRight;
+ currLum = lumRight;
+ }
+ if (lumUp < currLum) {
+ colOut = colUp;
+ currLum = lumUp;
+ }
+ if (lumDown < currLum) {
+ colOut = colDown;
+ currLum = lumDown;
+ }
- out[currIdx++] = colOut;
+ out[currIdx++] = colOut;
+ }
}
+ Filters._setPixels(pixels, out);
+ },
+
+ blur(canvas, radius) {
+ blurARGB(canvas, radius);
}
- Filters._setPixels(pixels, out);
};
// BLUR
@@ -627,8 +635,6 @@ function blurARGB(canvas, radius) {
Filters._setPixels(pixels, argb);
}
-Filters.blur = function(canvas, radius) {
- blurARGB(canvas, radius);
-};
+
export default Filters;
diff --git a/src/image/image.js b/src/image/image.js
index 90a7d4da80..b4d150df66 100644
--- a/src/image/image.js
+++ b/src/image/image.js
@@ -324,8 +324,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) {
// 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];
+ allFramesPixelColors[i].forEach((color, k) => {
if (localPaletteRequired) {
if (colorIndicesLookup[color] === undefined) {
colorIndicesLookup[color] = palette.length;
@@ -343,7 +342,7 @@ p5.prototype.encodeAndDownloadGif = function(pImg, filename) {
cannotBeTransparent.add(color);
}
}
- }
+ });
const frameOpts = {};
diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js
index 7c09f614d2..3e708ea97c 100644
--- a/src/image/loading_displaying.js
+++ b/src/image/loading_displaying.js
@@ -467,7 +467,7 @@ p5.prototype.saveGif = async function(
gif.writeFrame(indexedFrame, this.width, this.height, {
delay: gifFrameDelay,
transparent: true,
- transparentIndex: transparentIndex,
+ transparentIndex,
dispose: 1
});
}
@@ -516,14 +516,14 @@ function _flipPixels(pixels) {
// 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;
+ const halfHeight = parseInt(height / 2);
+ const 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;
+ const temp = new Uint8Array(width * 4);
+ for (let y = 0; y < halfHeight; ++y) {
+ const topOffset = y * bytesPerRow;
+ const bottomOffset = (height - y - 1) * bytesPerRow;
// make copy of a row on the top half
temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow));
diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js
index 59306ac688..36a5a162f8 100644
--- a/src/image/p5.Image.js
+++ b/src/image/p5.Image.js
@@ -84,103 +84,335 @@ import Filters from './filters';
* @param {Number} width
* @param {Number} height
*/
-p5.Image = function(width, height) {
+p5.Image = class {
+ constructor(width, height) {
+ /**
+ * Image width.
+ * @property {Number} width
+ * @readOnly
+ * @example
+ *
+ * let img;
+ * function preload() {
+ * img = loadImage('assets/rockies.jpg');
+ * }
+ *
+ * function setup() {
+ * createCanvas(100, 100);
+ * image(img, 0, 0);
+ * for (let i = 0; i < img.width; i++) {
+ * let c = img.get(i, img.height / 2);
+ * stroke(c);
+ * line(i, height / 2, i, height);
+ * }
+ * }
+ *
+ *
+ * @alt
+ * rocky mountains in top and horizontal lines in corresponding colors in bottom.
+ *
+ */
+ this.width = width;
+ /**
+ * Image height.
+ * @property {Number} height
+ * @readOnly
+ * @example
+ *
+ * let img;
+ * function preload() {
+ * img = loadImage('assets/rockies.jpg');
+ * }
+ *
+ * function setup() {
+ * createCanvas(100, 100);
+ * image(img, 0, 0);
+ * for (let i = 0; i < img.height; i++) {
+ * let c = img.get(img.width / 2, i);
+ * stroke(c);
+ * line(0, i, width / 2, i);
+ * }
+ * }
+ *
+ *
+ * @alt
+ * rocky mountains on right and vertical lines in corresponding colors on left.
+ *
+ */
+ this.height = height;
+ this.canvas = document.createElement('canvas');
+ this.canvas.width = this.width;
+ this.canvas.height = this.height;
+ this.drawingContext = this.canvas.getContext('2d');
+ this._pixelsState = this;
+ this._pixelDensity = 1;
+ //Object for working with GIFs, defaults to null
+ this.gifProperties = null;
+ //For WebGL Texturing only: used to determine whether to reupload texture to GPU
+ this._modified = false;
+ /**
+ * Array containing the values for all the pixels in the display window.
+ * These values are numbers. This array is the size (include an appropriate
+ * factor for pixelDensity) of the display window x4,
+ * representing the R, G, B, A values in order for each pixel, moving from
+ * left to right across each row, then down each column. Retina and other
+ * high density displays may have more pixels (by a factor of
+ * pixelDensity^2).
+ * For example, if the image is 100×100 pixels, there will be 40,000. With
+ * pixelDensity = 2, there will be 160,000. The first four values
+ * (indices 0-3) in the array will be the R, G, B, A values of the pixel at
+ * (0, 0). The second four values (indices 4-7) will contain the R, G, B, A
+ * values of the pixel at (1, 0). More generally, to set values for a pixel
+ * at (x, y):
+ * ```javascript
+ * let d = pixelDensity();
+ * for (let i = 0; i < d; i++) {
+ * for (let j = 0; j < d; j++) {
+ * // loop over
+ * index = 4 * ((y * d + j) * width * d + (x * d + i));
+ * pixels[index] = r;
+ * pixels[index+1] = g;
+ * pixels[index+2] = b;
+ * pixels[index+3] = a;
+ * }
+ * }
+ * ```
+ *
+ * Before accessing this array, the data must loaded with the loadPixels()
+ * function. After the array data has been modified, the updatePixels()
+ * function must be run to update the changes.
+ * @property {Number[]} pixels
+ * @example
+ *
+ *
+ * let img = createImage(66, 66);
+ * img.loadPixels();
+ * for (let i = 0; i < img.width; i++) {
+ * for (let j = 0; j < img.height; j++) {
+ * img.set(i, j, color(0, 90, 102));
+ * }
+ * }
+ * img.updatePixels();
+ * image(img, 17, 17);
+ *
+ *
+ *
+ *
+ * let pink = color(255, 102, 204);
+ * let img = createImage(66, 66);
+ * img.loadPixels();
+ * for (let i = 0; i < 4 * (width * height / 2); i += 4) {
+ * img.pixels[i] = red(pink);
+ * img.pixels[i + 1] = green(pink);
+ * img.pixels[i + 2] = blue(pink);
+ * img.pixels[i + 3] = alpha(pink);
+ * }
+ * img.updatePixels();
+ * image(img, 17, 17);
+ *
+ *
+ *
+ * @alt
+ * 66×66 turquoise rect in center of canvas
+ * 66×66 pink rect in center of canvas
+ *
+ */
+ this.pixels = [];
+ }
+
+ /**
+ * Helper function for animating GIF-based images with time
+ */
+ _animateGif(pInst) {
+ const props = this.gifProperties;
+ const curTime = pInst._lastRealFrameTime;
+ if (props.lastChangeTime === 0) {
+ props.lastChangeTime = curTime;
+ }
+ if (props.playing) {
+ props.timeDisplayed = curTime - props.lastChangeTime;
+ const curDelay = props.frames[props.displayIndex].delay;
+ if (props.timeDisplayed >= curDelay) {
+ //GIF is bound to 'realtime' so can skip frames
+ const skips = Math.floor(props.timeDisplayed / curDelay);
+ props.timeDisplayed = 0;
+ props.lastChangeTime = curTime;
+ props.displayIndex += skips;
+ props.loopCount = Math.floor(props.displayIndex / props.numFrames);
+ if (props.loopLimit !== null && props.loopCount >= props.loopLimit) {
+ props.playing = false;
+ } else {
+ const ind = props.displayIndex % props.numFrames;
+ this.drawingContext.putImageData(props.frames[ind].image, 0, 0);
+ props.displayIndex = ind;
+ this.setModified(true);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper fxn for sharing pixel methods
+ */
+ _setProperty(prop, value) {
+ this[prop] = value;
+ this.setModified(true);
+ }
+
/**
- * Image width.
- * @property {Number} width
- * @readOnly
+ * Loads the pixels data for this image into the [pixels] attribute.
+ *
+ * @method loadPixels
* @example
*
- * let img;
+ * let myImage;
+ * let halfImage;
+ *
* function preload() {
- * img = loadImage('assets/rockies.jpg');
+ * myImage = loadImage('assets/rockies.jpg');
* }
*
* function setup() {
- * createCanvas(100, 100);
- * image(img, 0, 0);
- * for (let i = 0; i < img.width; i++) {
- * let c = img.get(i, img.height / 2);
- * stroke(c);
- * line(i, height / 2, i, height);
+ * myImage.loadPixels();
+ * halfImage = 4 * myImage.width * myImage.height / 2;
+ * for (let i = 0; i < halfImage; i++) {
+ * myImage.pixels[i + halfImage] = myImage.pixels[i];
* }
+ * myImage.updatePixels();
+ * }
+ *
+ * function draw() {
+ * image(myImage, 0, 0, width, height);
* }
*
*
* @alt
- * rocky mountains in top and horizontal lines in corresponding colors in bottom.
- *
+ * 2 images of rocky mountains vertically stacked
*/
- this.width = width;
+ loadPixels() {
+ p5.Renderer2D.prototype.loadPixels.call(this);
+ this.setModified(true);
+ }
+
/**
- * Image height.
- * @property {Number} height
- * @readOnly
+ * Updates the backing canvas for this image with the contents of
+ * the [pixels] array.
+ *
+ * If this image is an animated GIF then the pixels will be updated
+ * in the frame that is currently displayed.
+ *
+ * @method updatePixels
+ * @param {Integer} x x-offset of the target update area for the
+ * underlying canvas
+ * @param {Integer} y y-offset of the target update area for the
+ * underlying canvas
+ * @param {Integer} w width of the target update area for the
+ * underlying canvas
+ * @param {Integer} h height of the target update area for the
+ * underlying canvas
* @example
*
- * let img;
+ * let myImage;
+ * let halfImage;
+ *
* function preload() {
- * img = loadImage('assets/rockies.jpg');
+ * myImage = loadImage('assets/rockies.jpg');
* }
*
* function setup() {
- * createCanvas(100, 100);
- * image(img, 0, 0);
- * for (let i = 0; i < img.height; i++) {
- * let c = img.get(img.width / 2, i);
- * stroke(c);
- * line(0, i, width / 2, i);
+ * myImage.loadPixels();
+ * halfImage = 4 * myImage.width * myImage.height / 2;
+ * for (let i = 0; i < halfImage; i++) {
+ * myImage.pixels[i + halfImage] = myImage.pixels[i];
* }
+ * myImage.updatePixels();
+ * }
+ *
+ * function draw() {
+ * image(myImage, 0, 0, width, height);
* }
*
*
* @alt
- * rocky mountains on right and vertical lines in corresponding colors on left.
- *
+ * 2 images of rocky mountains vertically stacked
*/
- this.height = height;
- this.canvas = document.createElement('canvas');
- this.canvas.width = this.width;
- this.canvas.height = this.height;
- this.drawingContext = this.canvas.getContext('2d');
- this._pixelsState = this;
- this._pixelDensity = 1;
- //Object for working with GIFs, defaults to null
- this.gifProperties = null;
- //For WebGL Texturing only: used to determine whether to reupload texture to GPU
- this._modified = false;
/**
- * Array containing the values for all the pixels in the display window.
- * These values are numbers. This array is the size (include an appropriate
- * factor for pixelDensity) of the display window x4,
- * representing the R, G, B, A values in order for each pixel, moving from
- * left to right across each row, then down each column. Retina and other
- * high density displays may have more pixels (by a factor of
- * pixelDensity^2).
- * For example, if the image is 100×100 pixels, there will be 40,000. With
- * pixelDensity = 2, there will be 160,000. The first four values
- * (indices 0-3) in the array will be the R, G, B, A values of the pixel at
- * (0, 0). The second four values (indices 4-7) will contain the R, G, B, A
- * values of the pixel at (1, 0). More generally, to set values for a pixel
- * at (x, y):
- * ```javascript
- * let d = pixelDensity();
- * for (let i = 0; i < d; i++) {
- * for (let j = 0; j < d; j++) {
- * // loop over
- * index = 4 * ((y * d + j) * width * d + (x * d + i));
- * pixels[index] = r;
- * pixels[index+1] = g;
- * pixels[index+2] = b;
- * pixels[index+3] = a;
- * }
+ * @method updatePixels
+ */
+ updatePixels(x, y, w, h) {
+ p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
+ this.setModified(true);
+ }
+
+ /**
+ * Get a region of pixels from an image.
+ *
+ * If no params are passed, the whole image is returned.
+ * If x and y are the only params passed a single pixel is extracted.
+ * If all params are passed a rectangle region is extracted and a p5.Image
+ * is returned.
+ *
+ * @method get
+ * @param {Number} x x-coordinate of the pixel
+ * @param {Number} y y-coordinate of the pixel
+ * @param {Number} w width
+ * @param {Number} h height
+ * @return {p5.Image} the rectangle p5.Image
+ * @example
+ *
+ * let myImage;
+ * let c;
+ *
+ * function preload() {
+ * myImage = loadImage('assets/rockies.jpg');
* }
- * ```
*
- * Before accessing this array, the data must loaded with the loadPixels()
- * function. After the array data has been modified, the updatePixels()
- * function must be run to update the changes.
- * @property {Number[]} pixels
+ * function setup() {
+ * background(myImage);
+ * noStroke();
+ * c = myImage.get(60, 90);
+ * fill(c);
+ * rect(25, 25, 50, 50);
+ * }
+ *
+ * //get() returns color here
+ *
+ *
+ * @alt
+ * image of rocky mountains with 50×50 green rect in front
+ */
+ /**
+ * @method get
+ * @return {p5.Image} the whole p5.Image
+ */
+ /**
+ * @method get
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Number[]} color of pixel at x,y in array format [R, G, B, A]
+ */
+ get(x, y, w, h) {
+ p5._validateParameters('p5.Image.get', arguments);
+ return p5.Renderer2D.prototype.get.apply(this, arguments);
+ }
+
+ _getPixel(...args) {
+ return p5.Renderer2D.prototype._getPixel.apply(this, args);
+ }
+
+ /**
+ * Set the color of a single pixel or write an image into
+ * this p5.Image.
+ *
+ * Note that for a large number of pixels this will
+ * be slower than directly manipulating the pixels array
+ * and then calling updatePixels().
+ *
+ * @method set
+ * @param {Number} x x-coordinate of the pixel
+ * @param {Number} y y-coordinate of the pixel
+ * @param {Number|Number[]|Object} a grayscale value | pixel array |
+ * a p5.Color | image to copy
* @example
*
*
@@ -188,1004 +420,775 @@ p5.Image = function(width, height) {
* img.loadPixels();
* for (let i = 0; i < img.width; i++) {
* for (let j = 0; j < img.height; j++) {
- * img.set(i, j, color(0, 90, 102));
+ * img.set(i, j, color(0, 90, 102, (i % img.width) * 2));
* }
* }
* img.updatePixels();
* image(img, 17, 17);
- *
- *
- *
- *
- * let pink = color(255, 102, 204);
- * let img = createImage(66, 66);
- * img.loadPixels();
- * for (let i = 0; i < 4 * (width * height / 2); i += 4) {
- * img.pixels[i] = red(pink);
- * img.pixels[i + 1] = green(pink);
- * img.pixels[i + 2] = blue(pink);
- * img.pixels[i + 3] = alpha(pink);
- * }
- * img.updatePixels();
- * image(img, 17, 17);
+ * image(img, 34, 34);
*
*
*
* @alt
- * 66×66 turquoise rect in center of canvas
- * 66×66 pink rect in center of canvas
+ * 2 gradated dark turquoise rects fade left. 1 center 1 bottom right of canvas
+ */
+ set(x, y, imgOrCol) {
+ p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
+ this.setModified(true);
+ }
+
+ /**
+ * Resize the image to a new width and height. To make the image scale
+ * proportionally, use 0 as the value for the wide or high parameter.
+ * For instance, to make the width of an image 150 pixels, and change
+ * the height using the same proportion, use resize(150, 0).
+ *
+ * @method resize
+ * @param {Number} width the resized image width
+ * @param {Number} height the resized image height
+ * @example
+ *
+ * let img;
+ *
+ * function preload() {
+ * img = loadImage('assets/rockies.jpg');
+ * }
+
+ * function draw() {
+ * image(img, 0, 0);
+ * }
+ *
+ * function mousePressed() {
+ * img.resize(50, 100);
+ * }
+ *
*
+ * @alt
+ * image of rocky mountains. zoomed in
*/
- this.pixels = [];
-};
+ resize(width, height) {
+ // Copy contents to a temporary canvas, resize the original
+ // and then copy back.
+ //
+ // There is a faster approach that involves just one copy and swapping the
+ // this.canvas reference. We could switch to that approach if (as i think
+ // is the case) there an expectation that the user would not hold a
+ // reference to the backing canvas of a p5.Image. But since we do not
+ // enforce that at the moment, I am leaving in the slower, but safer
+ // implementation.
-/**
- * Helper function for animating GIF-based images with time
- */
-p5.Image.prototype._animateGif = function(pInst) {
- const props = this.gifProperties;
- const curTime = pInst._lastRealFrameTime;
- if (props.lastChangeTime === 0) {
- props.lastChangeTime = curTime;
- }
- if (props.playing) {
- props.timeDisplayed = curTime - props.lastChangeTime;
- const curDelay = props.frames[props.displayIndex].delay;
- if (props.timeDisplayed >= curDelay) {
- //GIF is bound to 'realtime' so can skip frames
- const skips = Math.floor(props.timeDisplayed / curDelay);
- props.timeDisplayed = 0;
- props.lastChangeTime = curTime;
- props.displayIndex += skips;
- props.loopCount = Math.floor(props.displayIndex / props.numFrames);
- if (props.loopLimit !== null && props.loopCount >= props.loopLimit) {
- props.playing = false;
- } else {
- const ind = props.displayIndex % props.numFrames;
- this.drawingContext.putImageData(props.frames[ind].image, 0, 0);
- props.displayIndex = ind;
- this.setModified(true);
- }
+ // auto-resize
+ if (width === 0 && height === 0) {
+ width = this.canvas.width;
+ height = this.canvas.height;
+ } else if (width === 0) {
+ width = this.canvas.width * height / this.canvas.height;
+ } else if (height === 0) {
+ height = this.canvas.height * width / this.canvas.width;
}
- }
-};
-/**
- * Helper fxn for sharing pixel methods
- */
-p5.Image.prototype._setProperty = function(prop, value) {
- this[prop] = value;
- this.setModified(true);
-};
+ width = Math.floor(width);
+ height = Math.floor(height);
-/**
- * Loads the pixels data for this image into the [pixels] attribute.
- *
- * @method loadPixels
- * @example
- *
- * let myImage;
- * let halfImage;
- *
- * function preload() {
- * myImage = loadImage('assets/rockies.jpg');
- * }
- *
- * function setup() {
- * myImage.loadPixels();
- * halfImage = 4 * myImage.width * myImage.height / 2;
- * for (let i = 0; i < halfImage; i++) {
- * myImage.pixels[i + halfImage] = myImage.pixels[i];
- * }
- * myImage.updatePixels();
- * }
- *
- * function draw() {
- * image(myImage, 0, 0, width, height);
- * }
- *
- *
- * @alt
- * 2 images of rocky mountains vertically stacked
- */
-p5.Image.prototype.loadPixels = function() {
- p5.Renderer2D.prototype.loadPixels.call(this);
- this.setModified(true);
-};
+ const tempCanvas = document.createElement('canvas');
+ tempCanvas.width = width;
+ tempCanvas.height = height;
-/**
- * Updates the backing canvas for this image with the contents of
- * the [pixels] array.
- *
- * If this image is an animated GIF then the pixels will be updated
- * in the frame that is currently displayed.
- *
- * @method updatePixels
- * @param {Integer} x x-offset of the target update area for the
- * underlying canvas
- * @param {Integer} y y-offset of the target update area for the
- * underlying canvas
- * @param {Integer} w width of the target update area for the
- * underlying canvas
- * @param {Integer} h height of the target update area for the
- * underlying canvas
- * @example
- *
- * let myImage;
- * let halfImage;
- *
- * function preload() {
- * myImage = loadImage('assets/rockies.jpg');
- * }
- *
- * function setup() {
- * myImage.loadPixels();
- * halfImage = 4 * myImage.width * myImage.height / 2;
- * for (let i = 0; i < halfImage; i++) {
- * myImage.pixels[i + halfImage] = myImage.pixels[i];
- * }
- * myImage.updatePixels();
- * }
- *
- * function draw() {
- * image(myImage, 0, 0, width, height);
- * }
- *
- *
- * @alt
- * 2 images of rocky mountains vertically stacked
- */
-/**
- * @method updatePixels
- */
-p5.Image.prototype.updatePixels = function(x, y, w, h) {
- p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
- this.setModified(true);
-};
+ if (this.gifProperties) {
+ const props = this.gifProperties;
+ //adapted from github.com/LinusU/resize-image-data
+ const nearestNeighbor = (src, dst) => {
+ let pos = 0;
+ for (let y = 0; y < dst.height; y++) {
+ for (let x = 0; x < dst.width; x++) {
+ const srcX = Math.floor(x * src.width / dst.width);
+ const srcY = Math.floor(y * src.height / dst.height);
+ let srcPos = (srcY * src.width + srcX) * 4;
+ dst.data[pos++] = src.data[srcPos++]; // R
+ dst.data[pos++] = src.data[srcPos++]; // G
+ dst.data[pos++] = src.data[srcPos++]; // B
+ dst.data[pos++] = src.data[srcPos++]; // A
+ }
+ }
+ };
+ for (let i = 0; i < props.numFrames; i++) {
+ const resizedImageData = this.drawingContext.createImageData(
+ width,
+ height
+ );
+ nearestNeighbor(props.frames[i].image, resizedImageData);
+ props.frames[i].image = resizedImageData;
+ }
+ }
-/**
- * Get a region of pixels from an image.
- *
- * If no params are passed, the whole image is returned.
- * If x and y are the only params passed a single pixel is extracted.
- * If all params are passed a rectangle region is extracted and a p5.Image
- * is returned.
- *
- * @method get
- * @param {Number} x x-coordinate of the pixel
- * @param {Number} y y-coordinate of the pixel
- * @param {Number} w width
- * @param {Number} h height
- * @return {p5.Image} the rectangle p5.Image
- * @example
- *
- * let myImage;
- * let c;
- *
- * function preload() {
- * myImage = loadImage('assets/rockies.jpg');
- * }
- *
- * function setup() {
- * background(myImage);
- * noStroke();
- * c = myImage.get(60, 90);
- * fill(c);
- * rect(25, 25, 50, 50);
- * }
- *
- * //get() returns color here
- *
- *
- * @alt
- * image of rocky mountains with 50×50 green rect in front
- */
-/**
- * @method get
- * @return {p5.Image} the whole p5.Image
- */
-/**
- * @method get
- * @param {Number} x
- * @param {Number} y
- * @return {Number[]} color of pixel at x,y in array format [R, G, B, A]
- */
-p5.Image.prototype.get = function(x, y, w, h) {
- p5._validateParameters('p5.Image.get', arguments);
- return p5.Renderer2D.prototype.get.apply(this, arguments);
-};
+ tempCanvas.getContext('2d').drawImage(
+ this.canvas,
+ 0, 0, this.canvas.width, this.canvas.height,
+ 0, 0, tempCanvas.width, tempCanvas.height
+ );
-p5.Image.prototype._getPixel = p5.Renderer2D.prototype._getPixel;
+ // Resize the original canvas, which will clear its contents
+ this.canvas.width = this.width = width;
+ this.canvas.height = this.height = height;
-/**
- * Set the color of a single pixel or write an image into
- * this p5.Image.
- *
- * Note that for a large number of pixels this will
- * be slower than directly manipulating the pixels array
- * and then calling updatePixels().
- *
- * @method set
- * @param {Number} x x-coordinate of the pixel
- * @param {Number} y y-coordinate of the pixel
- * @param {Number|Number[]|Object} a grayscale value | pixel array |
- * a p5.Color | image to copy
- * @example
- *
- *
- * let img = createImage(66, 66);
- * img.loadPixels();
- * for (let i = 0; i < img.width; i++) {
- * for (let j = 0; j < img.height; j++) {
- * img.set(i, j, color(0, 90, 102, (i % img.width) * 2));
- * }
- * }
- * img.updatePixels();
- * image(img, 17, 17);
- * image(img, 34, 34);
- *
- *
- *
- * @alt
- * 2 gradated dark turquoise rects fade left. 1 center 1 bottom right of canvas
- */
-p5.Image.prototype.set = function(x, y, imgOrCol) {
- p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
- this.setModified(true);
-};
+ //Copy the image back
+ this.drawingContext.drawImage(
+ tempCanvas,
+ 0, 0, width, height,
+ 0, 0, width, height
+ );
-/**
- * Resize the image to a new width and height. To make the image scale
- * proportionally, use 0 as the value for the wide or high parameter.
- * For instance, to make the width of an image 150 pixels, and change
- * the height using the same proportion, use resize(150, 0).
- *
- * @method resize
- * @param {Number} width the resized image width
- * @param {Number} height the resized image height
- * @example
- *
- * let img;
- *
- * function preload() {
- * img = loadImage('assets/rockies.jpg');
- * }
+ if (this.pixels.length > 0) {
+ this.loadPixels();
+ }
- * function draw() {
- * image(img, 0, 0);
- * }
- *
- * function mousePressed() {
- * img.resize(50, 100);
- * }
- *
- *
- * @alt
- * image of rocky mountains. zoomed in
- */
-p5.Image.prototype.resize = function(width, height) {
- // Copy contents to a temporary canvas, resize the original
- // and then copy back.
- //
- // There is a faster approach that involves just one copy and swapping the
- // this.canvas reference. We could switch to that approach if (as i think
- // is the case) there an expectation that the user would not hold a
- // reference to the backing canvas of a p5.Image. But since we do not
- // enforce that at the moment, I am leaving in the slower, but safer
- // implementation.
+ this.setModified(true);
+ }
- // auto-resize
- if (width === 0 && height === 0) {
- width = this.canvas.width;
- height = this.canvas.height;
- } else if (width === 0) {
- width = this.canvas.width * height / this.canvas.height;
- } else if (height === 0) {
- height = this.canvas.height * width / this.canvas.width;
+ /**
+ * Copies a region of pixels from one image to another. If no
+ * srcImage is specified this is used as the source. If the source
+ * and destination regions aren't the same size, it will
+ * automatically resize source pixels to fit the specified
+ * target region.
+ *
+ * @method copy
+ * @param {p5.Image|p5.Element} srcImage source image
+ * @param {Integer} sx X coordinate of the source's upper left corner
+ * @param {Integer} sy Y coordinate of the source's upper left corner
+ * @param {Integer} sw source image width
+ * @param {Integer} sh source image height
+ * @param {Integer} dx X coordinate of the destination's upper left corner
+ * @param {Integer} dy Y coordinate of the destination's upper left corner
+ * @param {Integer} dw destination image width
+ * @param {Integer} dh destination image height
+ * @example
+ *
+ * let photo;
+ * let bricks;
+ * let x;
+ * let y;
+ *
+ * function preload() {
+ * photo = loadImage('assets/rockies.jpg');
+ * bricks = loadImage('assets/bricks.jpg');
+ * }
+ *
+ * function setup() {
+ * x = bricks.width / 2;
+ * y = bricks.height / 2;
+ * photo.copy(bricks, 0, 0, x, y, 0, 0, x, y);
+ * image(photo, 0, 0);
+ * }
+ *
+ *
+ * @alt
+ * image of rocky mountains and smaller image on top of bricks at top left
+ */
+ /**
+ * @method copy
+ * @param {Integer} sx
+ * @param {Integer} sy
+ * @param {Integer} sw
+ * @param {Integer} sh
+ * @param {Integer} dx
+ * @param {Integer} dy
+ * @param {Integer} dw
+ * @param {Integer} dh
+ */
+ copy(...args) {
+ p5.prototype.copy.apply(this, args);
}
- width = Math.floor(width);
- height = Math.floor(height);
+ /**
+ * Masks part of an image from displaying by loading another
+ * image and using its alpha channel as an alpha channel for
+ * this image. Masks are cumulative, once applied to an image
+ * object, they cannot be removed.
+ *
+ * @method mask
+ * @param {p5.Image} srcImage source image
+ * @example
+ *
+ * let photo, maskImage;
+ * function preload() {
+ * photo = loadImage('assets/rockies.jpg');
+ * maskImage = loadImage('assets/mask2.png');
+ * }
+ *
+ * function setup() {
+ * createCanvas(100, 100);
+ * photo.mask(maskImage);
+ * image(photo, 0, 0);
+ * }
+ *
+ *
+ * @alt
+ * image of rocky mountains with white at right
+ *
+ * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
+ */
+ // TODO: - Accept an array of alpha values.
+ mask(p5Image) {
+ if (p5Image === undefined) {
+ p5Image = this;
+ }
+ const currBlend = this.drawingContext.globalCompositeOperation;
- const tempCanvas = document.createElement('canvas');
- tempCanvas.width = width;
- tempCanvas.height = height;
+ let scaleFactor = 1;
+ if (p5Image instanceof p5.Renderer) {
+ scaleFactor = p5Image._pInst._pixelDensity;
+ }
- if (this.gifProperties) {
- const props = this.gifProperties;
- //adapted from github.com/LinusU/resize-image-data
- const nearestNeighbor = (src, dst) => {
- let pos = 0;
- for (let y = 0; y < dst.height; y++) {
- for (let x = 0; x < dst.width; x++) {
- const srcX = Math.floor(x * src.width / dst.width);
- const srcY = Math.floor(y * src.height / dst.height);
- let srcPos = (srcY * src.width + srcX) * 4;
- dst.data[pos++] = src.data[srcPos++]; // R
- dst.data[pos++] = src.data[srcPos++]; // G
- dst.data[pos++] = src.data[srcPos++]; // B
- dst.data[pos++] = src.data[srcPos++]; // A
- }
+ const copyArgs = [
+ p5Image,
+ 0,
+ 0,
+ scaleFactor * p5Image.width,
+ scaleFactor * p5Image.height,
+ 0,
+ 0,
+ this.width,
+ this.height
+ ];
+
+ this.drawingContext.globalCompositeOperation = 'destination-in';
+ if (this.gifProperties) {
+ for (let i = 0; i < this.gifProperties.frames.length; i++) {
+ this.drawingContext.putImageData(
+ this.gifProperties.frames[i].image,
+ 0,
+ 0
+ );
+ this.copy(...copyArgs);
+ this.gifProperties.frames[i].image = this.drawingContext.getImageData(
+ 0,
+ 0,
+ this.width,
+ this.height
+ );
}
- };
- for (let i = 0; i < props.numFrames; i++) {
- const resizedImageData = this.drawingContext.createImageData(
- width,
- height
+ this.drawingContext.putImageData(
+ this.gifProperties.frames[this.gifProperties.displayIndex].image,
+ 0,
+ 0
);
- nearestNeighbor(props.frames[i].image, resizedImageData);
- props.frames[i].image = resizedImageData;
+ } else {
+ this.copy(...copyArgs);
}
+ this.drawingContext.globalCompositeOperation = currBlend;
+ this.setModified(true);
}
- tempCanvas.getContext('2d').drawImage(
- this.canvas,
- 0, 0, this.canvas.width, this.canvas.height,
- 0, 0, tempCanvas.width, tempCanvas.height
- );
-
- // Resize the original canvas, which will clear its contents
- this.canvas.width = this.width = width;
- this.canvas.height = this.height = height;
-
- //Copy the image back
- this.drawingContext.drawImage(
- tempCanvas,
- 0, 0, width, height,
- 0, 0, width, height
- );
-
- if (this.pixels.length > 0) {
- this.loadPixels();
- }
-
- this.setModified(true);
-};
-
-/**
- * Copies a region of pixels from one image to another. If no
- * srcImage is specified this is used as the source. If the source
- * and destination regions aren't the same size, it will
- * automatically resize source pixels to fit the specified
- * target region.
- *
- * @method copy
- * @param {p5.Image|p5.Element} srcImage source image
- * @param {Integer} sx X coordinate of the source's upper left corner
- * @param {Integer} sy Y coordinate of the source's upper left corner
- * @param {Integer} sw source image width
- * @param {Integer} sh source image height
- * @param {Integer} dx X coordinate of the destination's upper left corner
- * @param {Integer} dy Y coordinate of the destination's upper left corner
- * @param {Integer} dw destination image width
- * @param {Integer} dh destination image height
- * @example
- *
- * let photo;
- * let bricks;
- * let x;
- * let y;
- *
- * function preload() {
- * photo = loadImage('assets/rockies.jpg');
- * bricks = loadImage('assets/bricks.jpg');
- * }
- *
- * function setup() {
- * x = bricks.width / 2;
- * y = bricks.height / 2;
- * photo.copy(bricks, 0, 0, x, y, 0, 0, x, y);
- * image(photo, 0, 0);
- * }
- *
- *
- * @alt
- * image of rocky mountains and smaller image on top of bricks at top left
- */
-/**
- * @method copy
- * @param {Integer} sx
- * @param {Integer} sy
- * @param {Integer} sw
- * @param {Integer} sh
- * @param {Integer} dx
- * @param {Integer} dy
- * @param {Integer} dw
- * @param {Integer} dh
- */
-p5.Image.prototype.copy = function(...args) {
- p5.prototype.copy.apply(this, args);
-};
-
-/**
- * Masks part of an image from displaying by loading another
- * image and using its alpha channel as an alpha channel for
- * this image. Masks are cumulative, once applied to an image
- * object, they cannot be removed.
- *
- * @method mask
- * @param {p5.Image} srcImage source image
- * @example
- *
- * let photo, maskImage;
- * function preload() {
- * photo = loadImage('assets/rockies.jpg');
- * maskImage = loadImage('assets/mask2.png');
- * }
- *
- * function setup() {
- * createCanvas(100, 100);
- * photo.mask(maskImage);
- * image(photo, 0, 0);
- * }
- *
- *
- * @alt
- * image of rocky mountains with white at right
- *
- * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
- */
-// TODO: - Accept an array of alpha values.
-p5.Image.prototype.mask = function(p5Image) {
- if (p5Image === undefined) {
- p5Image = this;
+ /**
+ * Applies an image filter to a p5.Image
+ *
+ * THRESHOLD
+ * Converts the image to black and white pixels depending if they are above or
+ * below the threshold defined by the level parameter. The parameter must be
+ * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is used.
+ *
+ * GRAY
+ * Converts any colors in the image to grayscale equivalents. No parameter
+ * is used.
+ *
+ * OPAQUE
+ * Sets the alpha channel to entirely opaque. No parameter is used.
+ *
+ * INVERT
+ * Sets each pixel to its inverse value. No parameter is used.
+ *
+ * POSTERIZE
+ * Limits each channel of the image to the number of colors specified as the
+ * parameter. The parameter can be set to values between 2 and 255, but
+ * results are most noticeable in the lower ranges.
+ *
+ * BLUR
+ * Executes a Gaussian blur with the level parameter specifying the extent
+ * of the blurring. If no parameter is used, the blur is equivalent to
+ * Gaussian blur of radius 1. Larger values increase the blur.
+ *
+ * ERODE
+ * Reduces the light areas. No parameter is used.
+ *
+ * DILATE
+ * Increases the light areas. No parameter is used.
+ *
+ * filter() does not work in WEBGL mode.
+ * A similar effect can be achieved in WEBGL mode using custom
+ * shaders. Adam Ferriss has written
+ * a selection of shader examples that contains many
+ * of the effects present in the filter examples.
+ *
+ * @method filter
+ * @param {Constant} filterType either THRESHOLD, GRAY, OPAQUE, INVERT,
+ * POSTERIZE, ERODE, DILATE or BLUR.
+ * See Filters.js for docs on
+ * each available filter
+ * @param {Number} [filterParam] an optional parameter unique
+ * to each filter, see above
+ * @example
+ *
+ * let photo1;
+ * let photo2;
+ *
+ * function preload() {
+ * photo1 = loadImage('assets/rockies.jpg');
+ * photo2 = loadImage('assets/rockies.jpg');
+ * }
+ *
+ * function setup() {
+ * photo2.filter(GRAY);
+ * image(photo1, 0, 0);
+ * image(photo2, width / 2, 0);
+ * }
+ *
+ *
+ * @alt
+ * 2 images of rocky mountains left one in color, right in black and white
+ */
+ filter(operation, value) {
+ Filters.apply(this.canvas, Filters[operation], value);
+ this.setModified(true);
}
- const currBlend = this.drawingContext.globalCompositeOperation;
- let scaleFactor = 1;
- if (p5Image instanceof p5.Renderer) {
- scaleFactor = p5Image._pInst._pixelDensity;
+ /**
+ * Copies a region of pixels from one image to another, using a specified
+ * blend mode to do the operation.
+ *
+ * @method blend
+ * @param {p5.Image} srcImage source image
+ * @param {Integer} sx X coordinate of the source's upper left corner
+ * @param {Integer} sy Y coordinate of the source's upper left corner
+ * @param {Integer} sw source image width
+ * @param {Integer} sh source image height
+ * @param {Integer} dx X coordinate of the destination's upper left corner
+ * @param {Integer} dy Y coordinate of the destination's upper left corner
+ * @param {Integer} dw destination image width
+ * @param {Integer} dh destination image height
+ * @param {Constant} blendMode the blend mode. either
+ * BLEND, DARKEST, LIGHTEST, DIFFERENCE,
+ * MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
+ * SOFT_LIGHT, DODGE, BURN, ADD or NORMAL.
+ *
+ * Available blend modes are: normal | multiply | screen | overlay |
+ * darken | lighten | color-dodge | color-burn | hard-light |
+ * soft-light | difference | exclusion | hue | saturation |
+ * color | luminosity
+ *
+ * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
+ * @example
+ *
+ * let mountains;
+ * let bricks;
+ *
+ * function preload() {
+ * mountains = loadImage('assets/rockies.jpg');
+ * bricks = loadImage('assets/bricks_third.jpg');
+ * }
+ *
+ * function setup() {
+ * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, ADD);
+ * image(mountains, 0, 0);
+ * image(bricks, 0, 0);
+ * }
+ *
+ *
+ * let mountains;
+ * let bricks;
+ *
+ * function preload() {
+ * mountains = loadImage('assets/rockies.jpg');
+ * bricks = loadImage('assets/bricks_third.jpg');
+ * }
+ *
+ * function setup() {
+ * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST);
+ * image(mountains, 0, 0);
+ * image(bricks, 0, 0);
+ * }
+ *
+ *
+ * let mountains;
+ * let bricks;
+ *
+ * function preload() {
+ * mountains = loadImage('assets/rockies.jpg');
+ * bricks = loadImage('assets/bricks_third.jpg');
+ * }
+ *
+ * function setup() {
+ * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST);
+ * image(mountains, 0, 0);
+ * image(bricks, 0, 0);
+ * }
+ *
+ *
+ * @alt
+ * image of rocky mountains. Brick images on left and right. Right overexposed
+ * image of rockies. Brickwall images on left and right. Right mortar transparent
+ * image of rockies. Brickwall images on left and right. Right translucent
+ */
+ /**
+ * @method blend
+ * @param {Integer} sx
+ * @param {Integer} sy
+ * @param {Integer} sw
+ * @param {Integer} sh
+ * @param {Integer} dx
+ * @param {Integer} dy
+ * @param {Integer} dw
+ * @param {Integer} dh
+ * @param {Constant} blendMode
+ */
+ blend(...args) {
+ p5._validateParameters('p5.Image.blend', arguments);
+ p5.prototype.blend.apply(this, args);
+ this.setModified(true);
}
- const copyArgs = [
- p5Image,
- 0,
- 0,
- scaleFactor * p5Image.width,
- scaleFactor * p5Image.height,
- 0,
- 0,
- this.width,
- this.height
- ];
-
- this.drawingContext.globalCompositeOperation = 'destination-in';
- if (this.gifProperties) {
- for (let i = 0; i < this.gifProperties.frames.length; i++) {
- this.drawingContext.putImageData(
- this.gifProperties.frames[i].image,
- 0,
- 0
- );
- p5.Image.prototype.copy.apply(this, copyArgs);
- this.gifProperties.frames[i].image = this.drawingContext.getImageData(
- 0,
- 0,
- this.width,
- this.height
- );
- }
- this.drawingContext.putImageData(
- this.gifProperties.frames[this.gifProperties.displayIndex].image,
- 0,
- 0
- );
- } else {
- p5.Image.prototype.copy.apply(this, copyArgs);
+ /**
+ * helper method for web GL mode to indicate that an image has been
+ * changed or unchanged since last upload. gl texture upload will
+ * set this value to false after uploading the texture.
+ * @method setModified
+ * @param {boolean} val sets whether or not the image has been
+ * modified.
+ * @private
+ */
+ setModified(val) {
+ this._modified = val; //enforce boolean?
}
- this.drawingContext.globalCompositeOperation = currBlend;
- this.setModified(true);
-};
-
-/**
- * Applies an image filter to a p5.Image
- *
- * THRESHOLD
- * Converts the image to black and white pixels depending if they are above or
- * below the threshold defined by the level parameter. The parameter must be
- * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is used.
- *
- * GRAY
- * Converts any colors in the image to grayscale equivalents. No parameter
- * is used.
- *
- * OPAQUE
- * Sets the alpha channel to entirely opaque. No parameter is used.
- *
- * INVERT
- * Sets each pixel to its inverse value. No parameter is used.
- *
- * POSTERIZE
- * Limits each channel of the image to the number of colors specified as the
- * parameter. The parameter can be set to values between 2 and 255, but
- * results are most noticeable in the lower ranges.
- *
- * BLUR
- * Executes a Gaussian blur with the level parameter specifying the extent
- * of the blurring. If no parameter is used, the blur is equivalent to
- * Gaussian blur of radius 1. Larger values increase the blur.
- *
- * ERODE
- * Reduces the light areas. No parameter is used.
- *
- * DILATE
- * Increases the light areas. No parameter is used.
- *
- * filter() does not work in WEBGL mode.
- * A similar effect can be achieved in WEBGL mode using custom
- * shaders. Adam Ferriss has written
- * a selection of shader examples that contains many
- * of the effects present in the filter examples.
- *
- * @method filter
- * @param {Constant} filterType either THRESHOLD, GRAY, OPAQUE, INVERT,
- * POSTERIZE, ERODE, DILATE or BLUR.
- * See Filters.js for docs on
- * each available filter
- * @param {Number} [filterParam] an optional parameter unique
- * to each filter, see above
- * @example
- *
- * let photo1;
- * let photo2;
- *
- * function preload() {
- * photo1 = loadImage('assets/rockies.jpg');
- * photo2 = loadImage('assets/rockies.jpg');
- * }
- *
- * function setup() {
- * photo2.filter(GRAY);
- * image(photo1, 0, 0);
- * image(photo2, width / 2, 0);
- * }
- *
- *
- * @alt
- * 2 images of rocky mountains left one in color, right in black and white
- */
-p5.Image.prototype.filter = function(operation, value) {
- Filters.apply(this.canvas, Filters[operation], value);
- this.setModified(true);
-};
-/**
- * Copies a region of pixels from one image to another, using a specified
- * blend mode to do the operation.
- *
- * @method blend
- * @param {p5.Image} srcImage source image
- * @param {Integer} sx X coordinate of the source's upper left corner
- * @param {Integer} sy Y coordinate of the source's upper left corner
- * @param {Integer} sw source image width
- * @param {Integer} sh source image height
- * @param {Integer} dx X coordinate of the destination's upper left corner
- * @param {Integer} dy Y coordinate of the destination's upper left corner
- * @param {Integer} dw destination image width
- * @param {Integer} dh destination image height
- * @param {Constant} blendMode the blend mode. either
- * BLEND, DARKEST, LIGHTEST, DIFFERENCE,
- * MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
- * SOFT_LIGHT, DODGE, BURN, ADD or NORMAL.
- *
- * Available blend modes are: normal | multiply | screen | overlay |
- * darken | lighten | color-dodge | color-burn | hard-light |
- * soft-light | difference | exclusion | hue | saturation |
- * color | luminosity
- *
- * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
- * @example
- *
- * let mountains;
- * let bricks;
- *
- * function preload() {
- * mountains = loadImage('assets/rockies.jpg');
- * bricks = loadImage('assets/bricks_third.jpg');
- * }
- *
- * function setup() {
- * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, ADD);
- * image(mountains, 0, 0);
- * image(bricks, 0, 0);
- * }
- *
- *
- * let mountains;
- * let bricks;
- *
- * function preload() {
- * mountains = loadImage('assets/rockies.jpg');
- * bricks = loadImage('assets/bricks_third.jpg');
- * }
- *
- * function setup() {
- * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST);
- * image(mountains, 0, 0);
- * image(bricks, 0, 0);
- * }
- *
- *
- * let mountains;
- * let bricks;
- *
- * function preload() {
- * mountains = loadImage('assets/rockies.jpg');
- * bricks = loadImage('assets/bricks_third.jpg');
- * }
- *
- * function setup() {
- * mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST);
- * image(mountains, 0, 0);
- * image(bricks, 0, 0);
- * }
- *
- *
- * @alt
- * image of rocky mountains. Brick images on left and right. Right overexposed
- * image of rockies. Brickwall images on left and right. Right mortar transparent
- * image of rockies. Brickwall images on left and right. Right translucent
- */
-/**
- * @method blend
- * @param {Integer} sx
- * @param {Integer} sy
- * @param {Integer} sw
- * @param {Integer} sh
- * @param {Integer} dx
- * @param {Integer} dy
- * @param {Integer} dw
- * @param {Integer} dh
- * @param {Constant} blendMode
- */
-p5.Image.prototype.blend = function(...args) {
- p5._validateParameters('p5.Image.blend', arguments);
- p5.prototype.blend.apply(this, args);
- this.setModified(true);
-};
-
-/**
- * helper method for web GL mode to indicate that an image has been
- * changed or unchanged since last upload. gl texture upload will
- * set this value to false after uploading the texture.
- * @method setModified
- * @param {boolean} val sets whether or not the image has been
- * modified.
- * @private
- */
-p5.Image.prototype.setModified = function(val) {
- this._modified = val; //enforce boolean?
-};
-
-/**
- * helper method for web GL mode to figure out if the image
- * has been modified and might need to be re-uploaded to texture
- * memory between frames.
- * @method isModified
- * @private
- * @return {boolean} a boolean indicating whether or not the
- * image has been updated or modified since last texture upload.
- */
-p5.Image.prototype.isModified = function() {
- return this._modified;
-};
+ /**
+ * helper method for web GL mode to figure out if the image
+ * has been modified and might need to be re-uploaded to texture
+ * memory between frames.
+ * @method isModified
+ * @private
+ * @return {boolean} a boolean indicating whether or not the
+ * image has been updated or modified since last texture upload.
+ */
+ isModified() {
+ return this._modified;
+ }
-/**
- * Saves the image to a file and force the browser to download it.
- * Accepts two strings for filename and file extension
- * Supports png (default), jpg, and gif
- *
- * Note that the file will only be downloaded as an animated GIF
- * if the p5.Image was loaded from a GIF file.
- * @method save
- * @param {String} filename give your file a name
- * @param {String} extension 'png' or 'jpg'
- * @example
- *
- * let photo;
- *
- * function preload() {
- * photo = loadImage('assets/rockies.jpg');
- * }
- *
- * function draw() {
- * image(photo, 0, 0);
- * }
- *
- * function keyTyped() {
- * if (key === 's') {
- * photo.save('photo', 'png');
- * }
- * }
- *
- *
- * @alt
- * image of rocky mountains.
- */
-p5.Image.prototype.save = function(filename, extension) {
- if (this.gifProperties) {
- p5.prototype.encodeAndDownloadGif(this, filename);
- } else {
- p5.prototype.saveCanvas(this.canvas, filename, extension);
+ /**
+ * Saves the image to a file and force the browser to download it.
+ * Accepts two strings for filename and file extension
+ * Supports png (default), jpg, and gif
+ *
+ * Note that the file will only be downloaded as an animated GIF
+ * if the p5.Image was loaded from a GIF file.
+ * @method save
+ * @param {String} filename give your file a name
+ * @param {String} extension 'png' or 'jpg'
+ * @example
+ *
+ * let photo;
+ *
+ * function preload() {
+ * photo = loadImage('assets/rockies.jpg');
+ * }
+ *
+ * function draw() {
+ * image(photo, 0, 0);
+ * }
+ *
+ * function keyTyped() {
+ * if (key === 's') {
+ * photo.save('photo', 'png');
+ * }
+ * }
+ *
+ *
+ * @alt
+ * image of rocky mountains.
+ */
+ save(filename, extension) {
+ if (this.gifProperties) {
+ p5.prototype.encodeAndDownloadGif(this, filename);
+ } else {
+ p5.prototype.saveCanvas(this.canvas, filename, extension);
+ }
}
-};
-// GIF Section
-/**
- * Starts an animated GIF over at the beginning state.
- *
- * @method reset
- * @example
- *
- * let gif;
- *
- * function preload() {
- * gif = loadImage('assets/arnott-wallace-wink-loop-once.gif');
- * }
- *
- * function draw() {
- * background(255);
- * // The GIF file that we loaded only loops once
- * // so it freezes on the last frame after playing through
- * image(gif, 0, 0);
- * }
- *
- * function mousePressed() {
- * // Click to reset the GIF and begin playback from start
- * gif.reset();
- * }
- *
- * @alt
- * Animated image of a cartoon face that winks once and then freezes
- * When you click it animates again, winks once and freezes
- */
-p5.Image.prototype.reset = function() {
- if (this.gifProperties) {
- const props = this.gifProperties;
- props.playing = true;
- props.timeSinceStart = 0;
- props.timeDisplayed = 0;
- props.lastChangeTime = 0;
- props.loopCount = 0;
- props.displayIndex = 0;
- this.drawingContext.putImageData(props.frames[0].image, 0, 0);
+ // GIF Section
+ /**
+ * Starts an animated GIF over at the beginning state.
+ *
+ * @method reset
+ * @example
+ *
+ * let gif;
+ *
+ * function preload() {
+ * gif = loadImage('assets/arnott-wallace-wink-loop-once.gif');
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * // The GIF file that we loaded only loops once
+ * // so it freezes on the last frame after playing through
+ * image(gif, 0, 0);
+ * }
+ *
+ * function mousePressed() {
+ * // Click to reset the GIF and begin playback from start
+ * gif.reset();
+ * }
+ *
+ * @alt
+ * Animated image of a cartoon face that winks once and then freezes
+ * When you click it animates again, winks once and freezes
+ */
+ reset() {
+ if (this.gifProperties) {
+ const props = this.gifProperties;
+ props.playing = true;
+ props.timeSinceStart = 0;
+ props.timeDisplayed = 0;
+ props.lastChangeTime = 0;
+ props.loopCount = 0;
+ props.displayIndex = 0;
+ this.drawingContext.putImageData(props.frames[0].image, 0, 0);
+ }
}
-};
-/**
- * Gets the index for the frame that is currently visible in an animated GIF.
- *
- * @method getCurrentFrame
- * @return {Number} The index for the currently displaying frame in animated GIF
- * @example
- *
- * let gif;
- *
- * function preload() {
- * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
- * }
- *
- * function draw() {
- * let frame = gif.getCurrentFrame();
- * image(gif, 0, 0);
- * text(frame, 10, 90);
- * }
- *
- * @alt
- * Animated image of a cartoon eye looking around and then
- * looking outwards, in the lower-left hand corner a number counts
- * up quickly to 124 and then starts back over at 0
- */
-p5.Image.prototype.getCurrentFrame = function() {
- if (this.gifProperties) {
- const props = this.gifProperties;
- return props.displayIndex % props.numFrames;
+ /**
+ * Gets the index for the frame that is currently visible in an animated GIF.
+ *
+ * @method getCurrentFrame
+ * @return {Number} The index for the currently displaying frame in animated GIF
+ * @example
+ *
+ * let gif;
+ *
+ * function preload() {
+ * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
+ * }
+ *
+ * function draw() {
+ * let frame = gif.getCurrentFrame();
+ * image(gif, 0, 0);
+ * text(frame, 10, 90);
+ * }
+ *
+ * @alt
+ * Animated image of a cartoon eye looking around and then
+ * looking outwards, in the lower-left hand corner a number counts
+ * up quickly to 124 and then starts back over at 0
+ */
+ getCurrentFrame() {
+ if (this.gifProperties) {
+ const props = this.gifProperties;
+ return props.displayIndex % props.numFrames;
+ }
}
-};
-/**
- * Sets the index of the frame that is currently visible in an animated GIF
- *
- * @method setFrame
- * @param {Number} index the index for the frame that should be displayed
- * @example
- *
- * let gif;
- *
- * function preload() {
- * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
- * }
- *
- * // Move your mouse up and down over canvas to see the GIF
- * // frames animate
- * function draw() {
- * gif.pause();
- * image(gif, 0, 0);
- * // Get the highest frame number which is the number of frames - 1
- * let maxFrame = gif.numFrames() - 1;
- * // Set the current frame that is mapped to be relative to mouse position
- * let frameNumber = floor(map(mouseY, 0, height, 0, maxFrame, true));
- * gif.setFrame(frameNumber);
- * }
- *
- * @alt
- * A still image of a cartoon eye that looks around when you move your mouse
- * up and down over the canvas
- */
-p5.Image.prototype.setFrame = function(index) {
- if (this.gifProperties) {
- const props = this.gifProperties;
- if (index < props.numFrames && index >= 0) {
- props.timeDisplayed = 0;
- props.lastChangeTime = 0;
- props.displayIndex = index;
- this.drawingContext.putImageData(props.frames[index].image, 0, 0);
- } else {
- console.log(
- 'Cannot set GIF to a frame number that is higher than total number of frames or below zero.'
- );
+ /**
+ * Sets the index of the frame that is currently visible in an animated GIF
+ *
+ * @method setFrame
+ * @param {Number} index the index for the frame that should be displayed
+ * @example
+ *
+ * let gif;
+ *
+ * function preload() {
+ * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
+ * }
+ *
+ * // Move your mouse up and down over canvas to see the GIF
+ * // frames animate
+ * function draw() {
+ * gif.pause();
+ * image(gif, 0, 0);
+ * // Get the highest frame number which is the number of frames - 1
+ * let maxFrame = gif.numFrames() - 1;
+ * // Set the current frame that is mapped to be relative to mouse position
+ * let frameNumber = floor(map(mouseY, 0, height, 0, maxFrame, true));
+ * gif.setFrame(frameNumber);
+ * }
+ *
+ * @alt
+ * A still image of a cartoon eye that looks around when you move your mouse
+ * up and down over the canvas
+ */
+ setFrame(index) {
+ if (this.gifProperties) {
+ const props = this.gifProperties;
+ if (index < props.numFrames && index >= 0) {
+ props.timeDisplayed = 0;
+ props.lastChangeTime = 0;
+ props.displayIndex = index;
+ this.drawingContext.putImageData(props.frames[index].image, 0, 0);
+ } else {
+ console.log(
+ 'Cannot set GIF to a frame number that is higher than total number of frames or below zero.'
+ );
+ }
}
}
-};
-/**
- * Returns the number of frames in an animated GIF
- *
- * @method numFrames
- * @return {Number}
- * @example The number of frames in the animated GIF
- *
- * let gif;
- *
- * function preload() {
- * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
- * }
- *
- * // Move your mouse up and down over canvas to see the GIF
- * // frames animate
- * function draw() {
- * gif.pause();
- * image(gif, 0, 0);
- * // Get the highest frame number which is the number of frames - 1
- * let maxFrame = gif.numFrames() - 1;
- * // Set the current frame that is mapped to be relative to mouse position
- * let frameNumber = floor(map(mouseY, 0, height, 0, maxFrame, true));
- * gif.setFrame(frameNumber);
- * }
- *
- * @alt
- * A still image of a cartoon eye that looks around when you move your mouse
- * up and down over the canvas
- */
-p5.Image.prototype.numFrames = function() {
- if (this.gifProperties) {
- return this.gifProperties.numFrames;
+ /**
+ * Returns the number of frames in an animated GIF
+ *
+ * @method numFrames
+ * @return {Number}
+ * @example The number of frames in the animated GIF
+ *
+ * let gif;
+ *
+ * function preload() {
+ * gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
+ * }
+ *
+ * // Move your mouse up and down over canvas to see the GIF
+ * // frames animate
+ * function draw() {
+ * gif.pause();
+ * image(gif, 0, 0);
+ * // Get the highest frame number which is the number of frames - 1
+ * let maxFrame = gif.numFrames() - 1;
+ * // Set the current frame that is mapped to be relative to mouse position
+ * let frameNumber = floor(map(mouseY, 0, height, 0, maxFrame, true));
+ * gif.setFrame(frameNumber);
+ * }
+ *
+ * @alt
+ * A still image of a cartoon eye that looks around when you move your mouse
+ * up and down over the canvas
+ */
+ numFrames() {
+ if (this.gifProperties) {
+ return this.gifProperties.numFrames;
+ }
}
-};
-/**
- * Plays an animated GIF that was paused with
- * pause()
- *
- * @method play
- * @example
- *
- * let gif;
- *
- * function preload() {
- * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif');
- * }
- *
- * function draw() {
- * background(255);
- * image(gif, 0, 0);
- * }
- *
- * function mousePressed() {
- * gif.pause();
- * }
- *
- * function mouseReleased() {
- * gif.play();
- * }
- *
- * @alt
- * An animated GIF of a drawing of small child with
- * hair blowing in the wind, when you click the image
- * freezes when you release it animates again
- */
-p5.Image.prototype.play = function() {
- if (this.gifProperties) {
- this.gifProperties.playing = true;
+ /**
+ * Plays an animated GIF that was paused with
+ * pause()
+ *
+ * @method play
+ * @example
+ *
+ * let gif;
+ *
+ * function preload() {
+ * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif');
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * image(gif, 0, 0);
+ * }
+ *
+ * function mousePressed() {
+ * gif.pause();
+ * }
+ *
+ * function mouseReleased() {
+ * gif.play();
+ * }
+ *
+ * @alt
+ * An animated GIF of a drawing of small child with
+ * hair blowing in the wind, when you click the image
+ * freezes when you release it animates again
+ */
+ play() {
+ if (this.gifProperties) {
+ this.gifProperties.playing = true;
+ }
}
-};
-/**
- * Pauses an animated GIF.
- *
- * @method pause
- * @example
- *
- * let gif;
- *
- * function preload() {
- * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif');
- * }
- *
- * function draw() {
- * background(255);
- * image(gif, 0, 0);
- * }
- *
- * function mousePressed() {
- * gif.pause();
- * }
- *
- * function mouseReleased() {
- * gif.play();
- * }
- *
- * @alt
- * An animated GIF of a drawing of small child with
- * hair blowing in the wind, when you click the image
- * freezes when you release it animates again
- */
-p5.Image.prototype.pause = function() {
- if (this.gifProperties) {
- this.gifProperties.playing = false;
+ /**
+ * Pauses an animated GIF.
+ *
+ * @method pause
+ * @example
+ *
+ * let gif;
+ *
+ * function preload() {
+ * gif = loadImage('assets/nancy-liang-wind-loop-forever.gif');
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * image(gif, 0, 0);
+ * }
+ *
+ * function mousePressed() {
+ * gif.pause();
+ * }
+ *
+ * function mouseReleased() {
+ * gif.play();
+ * }
+ *
+ * @alt
+ * An animated GIF of a drawing of small child with
+ * hair blowing in the wind, when you click the image
+ * freezes when you release it animates again
+ */
+ pause() {
+ if (this.gifProperties) {
+ this.gifProperties.playing = false;
+ }
}
-};
-/**
- * Changes the delay between frames in an animated GIF. There is an optional second parameter that
- * indicates an index for a specific frame that should have its delay modified. If no index is given, all frames
- * will have the new delay.
- *
- * @method delay
- * @param {Number} d the amount in milliseconds to delay between switching frames
- * @param {Number} [index] the index of the frame that should have the new delay value {optional}
- * @example
- *
- * let gifFast, gifSlow;
- *
- * function preload() {
- * gifFast = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
- * gifSlow = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
- * }
- *
- * function setup() {
- * gifFast.resize(width / 2, height / 2);
- * gifSlow.resize(width / 2, height / 2);
- *
- * //Change the delay here
- * gifFast.delay(10);
- * gifSlow.delay(100);
- * }
- *
- * function draw() {
- * background(255);
- * image(gifFast, 0, 0);
- * image(gifSlow, width / 2, 0);
- * }
- *
- * @alt
- * Two animated gifs of cartoon eyes looking around
- * The gif on the left animates quickly, on the right
- * the animation is much slower
- */
-p5.Image.prototype.delay = function(d, index) {
- if (this.gifProperties) {
- const props = this.gifProperties;
- if (index < props.numFrames && index >= 0) {
- props.frames[index].delay = d;
- } else {
- // change all frames
- for (const frame of props.frames) {
- frame.delay = d;
+ /**
+ * Changes the delay between frames in an animated GIF. There is an optional second parameter that
+ * indicates an index for a specific frame that should have its delay modified. If no index is given, all frames
+ * will have the new delay.
+ *
+ * @method delay
+ * @param {Number} d the amount in milliseconds to delay between switching frames
+ * @param {Number} [index] the index of the frame that should have the new delay value {optional}
+ * @example
+ *
+ * let gifFast, gifSlow;
+ *
+ * function preload() {
+ * gifFast = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
+ * gifSlow = loadImage('assets/arnott-wallace-eye-loop-forever.gif');
+ * }
+ *
+ * function setup() {
+ * gifFast.resize(width / 2, height / 2);
+ * gifSlow.resize(width / 2, height / 2);
+ *
+ * //Change the delay here
+ * gifFast.delay(10);
+ * gifSlow.delay(100);
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * image(gifFast, 0, 0);
+ * image(gifSlow, width / 2, 0);
+ * }
+ *
+ * @alt
+ * Two animated gifs of cartoon eyes looking around
+ * The gif on the left animates quickly, on the right
+ * the animation is much slower
+ */
+ delay(d, index) {
+ if (this.gifProperties) {
+ const props = this.gifProperties;
+ if (index < props.numFrames && index >= 0) {
+ props.frames[index].delay = d;
+ } else {
+ // change all frames
+ for (const frame of props.frames) {
+ frame.delay = d;
+ }
}
}
}
};
-
export default p5.Image;
diff --git a/src/io/files.js b/src/io/files.js
index 4835dbb3fa..8a0370c4f9 100644
--- a/src/io/files.js
+++ b/src/io/files.js
@@ -560,19 +560,16 @@ p5.prototype.loadTable = function(path) {
// helper function to turn a row into a JSON object
function makeObject(row, headers) {
- const ret = {};
headers = headers || [];
if (typeof headers === 'undefined') {
for (let j = 0; j < row.length; j++) {
headers[j.toString()] = j;
}
}
- for (let i = 0; i < headers.length; i++) {
- const key = headers[i];
- const val = row[i];
- ret[key] = val;
- }
- return ret;
+ return Object.fromEntries(
+ headers
+ .map((key,i) => [key, row[i]])
+ );
}
/**
@@ -1075,7 +1072,7 @@ p5.prototype.httpDo = function(...args) {
method,
mode: 'cors',
body: data,
- headers: headers
+ headers
});
}
// do some sort of smart type checking
diff --git a/src/io/p5.TableRow.js b/src/io/p5.TableRow.js
index 828d927adb..652c8521d6 100644
--- a/src/io/p5.TableRow.js
+++ b/src/io/p5.TableRow.js
@@ -23,18 +23,13 @@ import p5 from '../core/main';
p5.TableRow = class {
constructor(str, separator){
let arr = [];
- const obj = {};
if (str) {
separator = separator || ',';
arr = str.split(separator);
}
- for (let i = 0; i < arr.length; i++) {
- const key = i;
- const val = arr[i];
- obj[key] = val;
- }
+
this.arr = arr;
- this.obj = obj;
+ this.obj = Object.fromEntries(arr.entries());
this.table = null;
}
diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js
index a5cf117c84..1fce30cd77 100644
--- a/src/webgl/3d_primitives.js
+++ b/src/webgl/3d_primitives.js
@@ -174,8 +174,8 @@ p5.prototype.box = function(width, height, depth, detailX, detailY) {
[20, 21],
[22, 23]
];
- for (let i = 0; i < cubeIndices.length; i++) {
- const cubeIndex = cubeIndices[i];
+
+ cubeIndices.forEach((cubeIndex, i) => {
const v = i * 4;
for (let j = 0; j < 4; j++) {
const d = cubeIndex[j];
@@ -192,7 +192,7 @@ p5.prototype.box = function(width, height, depth, detailX, detailY) {
}
this.faces.push([v, v + 1, v + 2]);
this.faces.push([v + 2, v + 1, v + 3]);
- }
+ });
};
const boxGeom = new p5.Geometry(detailX, detailY, _box);
boxGeom.computeNormals();
diff --git a/src/webgl/interaction.js b/src/webgl/interaction.js
index d9015b15f0..26f09ff503 100644
--- a/src/webgl/interaction.js
+++ b/src/webgl/interaction.js
@@ -107,10 +107,9 @@ p5.prototype.orbitControl = function(
// get moved touches.
const movedTouches = [];
- for (let i = 0; i < this.touches.length; i++) {
- const curTouch = this.touches[i];
- for (let k = 0; k < this._renderer.prevTouches.length; k++) {
- const prevTouch = this._renderer.prevTouches[k];
+
+ this.touches.forEach(curTouch => {
+ this._renderer.prevTouches.forEach(prevTouch => {
if (curTouch.id === prevTouch.id) {
const movedTouch = {
x: curTouch.x,
@@ -120,8 +119,9 @@ p5.prototype.orbitControl = function(
};
movedTouches.push(movedTouch);
}
- }
- }
+ });
+ });
+
this._renderer.prevTouches = this.touches;
// The idea of using damping is based on the following website. thank you.
@@ -288,7 +288,7 @@ p5.prototype.orbitControl = function(
// Translate the camera so that the entire object moves
// perpendicular to the line of sight when the mouse is moved
// or when the centers of gravity of the two touch pointers move.
- var local = cam._getLocalAxes();
+ const local = cam._getLocalAxes();
// Calculate the z coordinate in the view coordinates of
// the center, that is, the distance to the view point
diff --git a/src/webgl/light.js b/src/webgl/light.js
index 552c7e9d5d..3fbab91e7b 100644
--- a/src/webgl/light.js
+++ b/src/webgl/light.js
@@ -1047,9 +1047,9 @@ p5.prototype.spotLight = function(
* Three white spheres. Each appears as a different
* color due to lighting.
*/
-p5.prototype.noLights = function() {
+p5.prototype.noLights = function(...args) {
this._assert3d('noLights');
- p5._validateParameters('noLights', arguments);
+ p5._validateParameters('noLights', args);
this._renderer._enableLighting = false;
diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js
index 34eee396e6..4d05315bf5 100644
--- a/src/webgl/p5.Geometry.js
+++ b/src/webgl/p5.Geometry.js
@@ -151,8 +151,7 @@ p5.Geometry = class {
// loop through all the faces adding its normal to the normal
// of each of its vertices
- for (let f = 0; f < faces.length; ++f) {
- const face = faces[f];
+ faces.forEach((face, f) => {
const faceNormal = this._getFaceNormal(f);
// all three vertices get the normal added
@@ -160,7 +159,7 @@ p5.Geometry = class {
const vertexIndex = face[fv];
vertexNormals[vertexIndex].add(faceNormal);
}
- }
+ });
// normalize the normals
for (iv = 0; iv < vertices.length; ++iv) {
diff --git a/src/webgl/p5.Matrix.js b/src/webgl/p5.Matrix.js
index 701916f167..add1da0657 100644
--- a/src/webgl/p5.Matrix.js
+++ b/src/webgl/p5.Matrix.js
@@ -347,20 +347,20 @@ p5.Matrix = class {
* @chainable
* @todo finish implementation
*/
- inverseTranspose(matrix) {
+ inverseTranspose({ mat4 }) {
if (this.mat3 === undefined) {
p5._friendlyError('sorry, this function only works with mat3');
} else {
//convert mat4 -> mat3
- this.mat3[0] = matrix.mat4[0];
- this.mat3[1] = matrix.mat4[1];
- this.mat3[2] = matrix.mat4[2];
- this.mat3[3] = matrix.mat4[4];
- this.mat3[4] = matrix.mat4[5];
- this.mat3[5] = matrix.mat4[6];
- this.mat3[6] = matrix.mat4[8];
- this.mat3[7] = matrix.mat4[9];
- this.mat3[8] = matrix.mat4[10];
+ this.mat3[0] = mat4[0];
+ this.mat3[1] = mat4[1];
+ this.mat3[2] = mat4[2];
+ this.mat3[3] = mat4[4];
+ this.mat3[4] = mat4[5];
+ this.mat3[5] = mat4[6];
+ this.mat3[6] = mat4[8];
+ this.mat3[7] = mat4[9];
+ this.mat3[8] = mat4[10];
}
const inverse = this.invert3x3();
@@ -749,8 +749,8 @@ p5.Matrix = class {
* @param {p5.Vector}
* @return {p5.Vector}
*/
- multiplyPoint(v) {
- const array = this.multiplyVec4(v.x, v.y, v.z, 1);
+ multiplyPoint({ x, y, z }) {
+ const array = this.multiplyVec4(x, y, z, 1);
return new p5.Vector(array[0], array[1], array[2]);
}
@@ -764,8 +764,8 @@ p5.Matrix = class {
* @param {p5.Vector}
* @return {p5.Vector}
*/
- multiplyAndNormalizePoint(v) {
- const array = this.multiplyVec4(v.x, v.y, v.z, 1);
+ multiplyAndNormalizePoint({ x, y, z }) {
+ const array = this.multiplyVec4(x, y, z, 1);
array[0] /= array[3];
array[1] /= array[3];
array[2] /= array[3];
@@ -782,8 +782,8 @@ p5.Matrix = class {
* @param {p5.Vector}
* @return {p5.Vector}
*/
- multiplyDirection(v) {
- const array = this.multiplyVec4(v.x, v.y, v.z, 0);
+ multiplyDirection({ x, y, z }) {
+ const array = this.multiplyVec4(x, y, z, 0);
return new p5.Vector(array[0], array[1], array[2]);
}
diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js
index 714d29f982..442d37ddfa 100644
--- a/src/webgl/p5.RendererGL.Immediate.js
+++ b/src/webgl/p5.RendererGL.Immediate.js
@@ -110,7 +110,7 @@ p5.RendererGL.prototype.vertex = function(x, y) {
vertexColor[2],
vertexColor[3]
);
- var lineVertexColor = this.curStrokeColor || [0.5, 0.5, 0.5, 1];
+ const lineVertexColor = this.curStrokeColor || [0.5, 0.5, 0.5, 1];
this.immediateMode.geometry.vertexStrokeColors.push(
lineVertexColor[0],
lineVertexColor[1],
diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js
index f5f28e60f7..c85c873303 100644
--- a/src/webgl/p5.RendererGL.js
+++ b/src/webgl/p5.RendererGL.js
@@ -77,332 +77,6 @@ const defaultShaders = {
pointFrag: readFileSync(join(__dirname, '/shaders/point.frag'), 'utf-8')
};
-/**
- * 3D graphics class
- * @private
- * @class p5.RendererGL
- * @constructor
- * @extends p5.Renderer
- * @todo extend class to include public method for offscreen
- * rendering (FBO).
- */
-p5.RendererGL = function (elt, pInst, isMainCanvas, attr) {
- p5.Renderer.call(this, elt, pInst, isMainCanvas);
- this._setAttributeDefaults(pInst);
- this._initContext();
- this.isP3D = true; //lets us know we're in 3d mode
-
- // This redundant property is useful in reminding you that you are
- // interacting with WebGLRenderingContext, still worth considering future removal
- this.GL = this.drawingContext;
- this._pInst._setProperty('drawingContext', this.drawingContext);
-
- // erasing
- this._isErasing = false;
-
- // lights
- this._enableLighting = false;
-
- this.ambientLightColors = [];
- this.specularColors = [1, 1, 1];
-
- this.directionalLightDirections = [];
- this.directionalLightDiffuseColors = [];
- this.directionalLightSpecularColors = [];
-
- this.pointLightPositions = [];
- this.pointLightDiffuseColors = [];
- this.pointLightSpecularColors = [];
-
- this.spotLightPositions = [];
- this.spotLightDirections = [];
- this.spotLightDiffuseColors = [];
- this.spotLightSpecularColors = [];
- this.spotLightAngle = [];
- this.spotLightConc = [];
-
- this.drawMode = constants.FILL;
-
- this.curFillColor = this._cachedFillStyle = [1, 1, 1, 1];
- this.curAmbientColor = this._cachedFillStyle = [1, 1, 1, 1];
- this.curSpecularColor = this._cachedFillStyle = [0, 0, 0, 0];
- this.curEmissiveColor = this._cachedFillStyle = [0, 0, 0, 0];
- this.curStrokeColor = this._cachedStrokeStyle = [0, 0, 0, 1];
-
- this.curBlendMode = constants.BLEND;
- this._cachedBlendMode = undefined;
- if (this.webglVersion === constants.WEBGL2) {
- this.blendExt = this.GL;
- } else {
- this.blendExt = this.GL.getExtension('EXT_blend_minmax');
- }
- this._isBlending = false;
-
-
- this._hasSetAmbient = false;
- this._useSpecularMaterial = false;
- this._useEmissiveMaterial = false;
- this._useNormalMaterial = false;
- this._useShininess = 1;
-
- this._useLineColor = false;
- this._useVertexColor = false;
-
- this.registerEnabled = [];
-
- this._tint = [255, 255, 255, 255];
-
- // lightFalloff variables
- this.constantAttenuation = 1;
- this.linearAttenuation = 0;
- this.quadraticAttenuation = 0;
-
- /**
- * model view, projection, & normal
- * matrices
- */
- this.uMVMatrix = new p5.Matrix();
- this.uPMatrix = new p5.Matrix();
- this.uNMatrix = new p5.Matrix('mat3');
-
- // Current vertex normal
- this._currentNormal = new p5.Vector(0, 0, 1);
-
- // Camera
- this._curCamera = new p5.Camera(this);
- this._curCamera._computeCameraDefaultSettings();
- this._curCamera._setDefaultCamera();
-
- // Information about the previous frame's touch object
- // for executing orbitControl()
- this.prevTouches = [];
- // Velocity variable for use with orbitControl()
- this.zoomVelocity = 0;
- this.rotateVelocity = new p5.Vector(0, 0);
- this.moveVelocity = new p5.Vector(0, 0);
- // Flags for recording the state of zooming, rotation and moving
- this.executeZoom = false;
- this.executeRotateAndMove = false;
-
- this._defaultLightShader = undefined;
- this._defaultImmediateModeShader = undefined;
- this._defaultNormalShader = undefined;
- this._defaultColorShader = undefined;
- this._defaultPointShader = undefined;
-
- this.userFillShader = undefined;
- this.userStrokeShader = undefined;
- this.userPointShader = undefined;
-
- // Default drawing is done in Retained Mode
- // Geometry and Material hashes stored here
- this.retainedMode = {
- geometry: {},
- buffers: {
- stroke: [
- new p5.RenderBuffer(4, 'lineVertexColors', 'lineColorBuffer', 'aVertexColor', this, this._flatten),
- new p5.RenderBuffer(3, 'lineVertices', 'lineVerticesBuffer', 'aPosition', this, this._flatten),
- new p5.RenderBuffer(3, 'lineTangentsIn', 'lineTangentsInBuffer', 'aTangentIn', this, this._flatten),
- new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this, this._flatten),
- new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this)
- ],
- fill: [
- new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
- new p5.RenderBuffer(3, 'vertexNormals', 'normalBuffer', 'aNormal', this, this._vToNArray),
- new p5.RenderBuffer(4, 'vertexColors', 'colorBuffer', 'aVertexColor', this),
- new p5.RenderBuffer(3, 'vertexAmbients', 'ambientBuffer', 'aAmbientColor', this),
- //new BufferDef(3, 'vertexSpeculars', 'specularBuffer', 'aSpecularColor'),
- new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
- ],
- text: [
- new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
- new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
- ]
- }
- };
-
- // Immediate Mode
- // Geometry and Material hashes stored here
- this.immediateMode = {
- geometry: new p5.Geometry(),
- shapeMode: constants.TRIANGLE_FAN,
- _bezierVertex: [],
- _quadraticVertex: [],
- _curveVertex: [],
- buffers: {
- fill: [
- new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
- new p5.RenderBuffer(3, 'vertexNormals', 'normalBuffer', 'aNormal', this, this._vToNArray),
- new p5.RenderBuffer(4, 'vertexColors', 'colorBuffer', 'aVertexColor', this),
- new p5.RenderBuffer(3, 'vertexAmbients', 'ambientBuffer', 'aAmbientColor', this),
- new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
- ],
- stroke: [
- new p5.RenderBuffer(4, 'lineVertexColors', 'lineColorBuffer', 'aVertexColor', this, this._flatten),
- new p5.RenderBuffer(3, 'lineVertices', 'lineVerticesBuffer', 'aPosition', this, this._flatten),
- new p5.RenderBuffer(3, 'lineTangentsIn', 'lineTangentsInBuffer', 'aTangentIn', this, this._flatten),
- new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this, this._flatten),
- new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this)
- ],
- point: this.GL.createBuffer()
- }
- };
-
- this.pointSize = 5.0; //default point size
- this.curStrokeWeight = 1;
- this.curStrokeCap = constants.ROUND;
- this.curStrokeJoin = constants.ROUND;
-
- // map of texture sources to textures created in this gl context via this.getTexture(src)
- this.textures = new Map();
-
- // set of framebuffers in use
- this.framebuffers = new Set();
-
- this.textureMode = constants.IMAGE;
- // default wrap settings
- this.textureWrapX = constants.CLAMP;
- this.textureWrapY = constants.CLAMP;
- this._tex = null;
- this._curveTightness = 6;
-
- // lookUpTable for coefficients needed to be calculated for bezierVertex, same are used for curveVertex
- this._lookUpTableBezier = [];
- // lookUpTable for coefficients needed to be calculated for quadraticVertex
- this._lookUpTableQuadratic = [];
-
- // current curveDetail in the Bezier lookUpTable
- this._lutBezierDetail = 0;
- // current curveDetail in the Quadratic lookUpTable
- this._lutQuadraticDetail = 0;
-
- // Used to distinguish between user calls to vertex() and internal calls
- this.isProcessingVertices = false;
- this._tessy = this._initTessy();
-
- this.fontInfos = {};
-
- this._curShader = undefined;
-
- return this;
-};
-
-p5.RendererGL.prototype = Object.create(p5.Renderer.prototype);
-
-//////////////////////////////////////////////
-// Setting
-//////////////////////////////////////////////
-
-p5.RendererGL.prototype._setAttributeDefaults = function (pInst) {
- // See issue #3850, safer to enable AA in Safari
- const applyAA = navigator.userAgent.toLowerCase().includes('safari');
- const defaults = {
- alpha: true,
- depth: true,
- stencil: true,
- antialias: applyAA,
- premultipliedAlpha: true,
- preserveDrawingBuffer: true,
- perPixelLighting: true,
- version: 2
- };
- if (pInst._glAttributes === null) {
- pInst._glAttributes = defaults;
- } else {
- pInst._glAttributes = Object.assign(defaults, pInst._glAttributes);
- }
- return;
-};
-
-p5.RendererGL.prototype._initContext = function () {
- if (this._pInst._glAttributes.version !== 1) {
- // Unless WebGL1 is explicitly asked for, try to create a WebGL2 context
- this.drawingContext =
- this.canvas.getContext('webgl2', this._pInst._glAttributes);
- }
- this.webglVersion = this.drawingContext ? constants.WEBGL2 : constants.WEBGL;
- // If this is the main canvas, make sure the global `webglVersion` is set
- this._pInst._setProperty('webglVersion', this.webglVersion);
- if (!this.drawingContext) {
- // If we were unable to create a WebGL2 context (either because it was
- // disabled via `setAttributes({ version: 1 })` or because the device
- // doesn't support it), fall back to a WebGL1 context
- this.drawingContext =
- this.canvas.getContext('webgl', this._pInst._glAttributes) ||
- this.canvas.getContext('experimental-webgl', this._pInst._glAttributes);
- }
- if (this.drawingContext === null) {
- throw new Error('Error creating webgl context');
- } else {
- const gl = this.drawingContext;
- gl.enable(gl.DEPTH_TEST);
- gl.depthFunc(gl.LEQUAL);
- gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
- // Make sure all images are loaded into the canvas premultiplied so that
- // they match the way we render colors. This will make framebuffer textures
- // be encoded the same way as textures from everything else.
- gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
- this._viewport = this.drawingContext.getParameter(
- this.drawingContext.VIEWPORT
- );
- }
-};
-
-//This is helper function to reset the context anytime the attributes
-//are changed with setAttributes()
-
-p5.RendererGL.prototype._resetContext = function (options, callback) {
- const w = this.width;
- const h = this.height;
- const defaultId = this.canvas.id;
- const isPGraphics = this._pInst instanceof p5.Graphics;
-
- if (isPGraphics) {
- const pg = this._pInst;
- pg.canvas.parentNode.removeChild(pg.canvas);
- pg.canvas = document.createElement('canvas');
- const node = pg._pInst._userNode || document.body;
- node.appendChild(pg.canvas);
- p5.Element.call(pg, pg.canvas, pg._pInst);
- pg.width = w;
- pg.height = h;
- } else {
- let c = this.canvas;
- if (c) {
- c.parentNode.removeChild(c);
- }
- c = document.createElement('canvas');
- c.id = defaultId;
- if (this._pInst._userNode) {
- this._pInst._userNode.appendChild(c);
- } else {
- document.body.appendChild(c);
- }
- this._pInst.canvas = c;
- this.canvas = c;
- }
-
- const renderer = new p5.RendererGL(
- this._pInst.canvas,
- this._pInst,
- !isPGraphics
- );
- this._pInst._setProperty('_renderer', renderer);
- renderer.resize(w, h);
- renderer._applyDefaults();
-
- if (!isPGraphics) {
- this._pInst._elements.push(renderer);
- }
-
- if (typeof callback === 'function') {
- //setTimeout with 0 forces the task to the back of the queue, this ensures that
- //we finish switching out the renderer
- setTimeout(() => {
- callback.apply(window._renderer, options);
- }, 0);
- }
-};
/**
* @module Rendering
* @submodule Rendering
@@ -557,7 +231,6 @@ p5.RendererGL.prototype._resetContext = function (options, callback) {
* @for p5
* @param {Object} obj object with key-value pairs
*/
-
p5.prototype.setAttributes = function (key, value) {
if (typeof this._glAttributes === 'undefined') {
console.log(
@@ -609,349 +282,6 @@ p5.prototype.setAttributes = function (key, value) {
this._renderer._curCamera._renderer = this._renderer;
}
};
-
-/**
- * @class p5.RendererGL
- */
-
-p5.RendererGL.prototype._update = function () {
- // reset model view and apply initial camera transform
- // (containing only look at info; no projection).
- this.uMVMatrix.set(
- this._curCamera.cameraMatrix.mat4[0],
- this._curCamera.cameraMatrix.mat4[1],
- this._curCamera.cameraMatrix.mat4[2],
- this._curCamera.cameraMatrix.mat4[3],
- this._curCamera.cameraMatrix.mat4[4],
- this._curCamera.cameraMatrix.mat4[5],
- this._curCamera.cameraMatrix.mat4[6],
- this._curCamera.cameraMatrix.mat4[7],
- this._curCamera.cameraMatrix.mat4[8],
- this._curCamera.cameraMatrix.mat4[9],
- this._curCamera.cameraMatrix.mat4[10],
- this._curCamera.cameraMatrix.mat4[11],
- this._curCamera.cameraMatrix.mat4[12],
- this._curCamera.cameraMatrix.mat4[13],
- this._curCamera.cameraMatrix.mat4[14],
- this._curCamera.cameraMatrix.mat4[15]
- );
-
- // reset light data for new frame.
-
- this.ambientLightColors.length = 0;
- this.specularColors = [1, 1, 1];
-
- this.directionalLightDirections.length = 0;
- this.directionalLightDiffuseColors.length = 0;
- this.directionalLightSpecularColors.length = 0;
-
- this.pointLightPositions.length = 0;
- this.pointLightDiffuseColors.length = 0;
- this.pointLightSpecularColors.length = 0;
-
- this.spotLightPositions.length = 0;
- this.spotLightDirections.length = 0;
- this.spotLightDiffuseColors.length = 0;
- this.spotLightSpecularColors.length = 0;
- this.spotLightAngle.length = 0;
- this.spotLightConc.length = 0;
-
- this._enableLighting = false;
-
- //reset tint value for new frame
- this._tint = [255, 255, 255, 255];
-
- //Clear depth every frame
- this.GL.clear(this.GL.DEPTH_BUFFER_BIT);
-};
-
-/**
- * [background description]
- */
-p5.RendererGL.prototype.background = function (...args) {
- const _col = this._pInst.color(...args);
- const _r = _col.levels[0] / 255;
- const _g = _col.levels[1] / 255;
- const _b = _col.levels[2] / 255;
- const _a = _col.levels[3] / 255;
- this.clear(_r, _g, _b, _a);
-};
-
-//////////////////////////////////////////////
-// COLOR
-//////////////////////////////////////////////
-/**
- * Basic fill material for geometry with a given color
- * @method fill
- * @class p5.RendererGL
- * @param {Number|Number[]|String|p5.Color} v1 gray value,
- * red or hue value (depending on the current color mode),
- * or color Array, or CSS color string
- * @param {Number} [v2] green or saturation value
- * @param {Number} [v3] blue or brightness value
- * @param {Number} [a] opacity
- * @chainable
- * @example
- *
- *
- * function setup() {
- * createCanvas(200, 200, WEBGL);
- * }
- *
- * function draw() {
- * background(0);
- * noStroke();
- * fill(100, 100, 240);
- * rotateX(frameCount * 0.01);
- * rotateY(frameCount * 0.01);
- * box(75, 75, 75);
- * }
- *
- *
- *
- * @alt
- * black canvas with purple cube spinning
- */
-p5.RendererGL.prototype.fill = function (v1, v2, v3, a) {
- //see material.js for more info on color blending in webgl
- const color = p5.prototype.color.apply(this._pInst, arguments);
- this.curFillColor = color._array;
- this.drawMode = constants.FILL;
- this._useNormalMaterial = false;
- this._tex = null;
-};
-
-/**
- * Basic stroke material for geometry with a given color
- * @method stroke
- * @param {Number|Number[]|String|p5.Color} v1 gray value,
- * red or hue value (depending on the current color mode),
- * or color Array, or CSS color string
- * @param {Number} [v2] green or saturation value
- * @param {Number} [v3] blue or brightness value
- * @param {Number} [a] opacity
- * @example
- *
- *
- * function setup() {
- * createCanvas(200, 200, WEBGL);
- * }
- *
- * function draw() {
- * background(0);
- * stroke(240, 150, 150);
- * fill(100, 100, 240);
- * rotateX(frameCount * 0.01);
- * rotateY(frameCount * 0.01);
- * box(75, 75, 75);
- * }
- *
- *
- *
- * @alt
- * black canvas with purple cube with pink outline spinning
- */
-p5.RendererGL.prototype.stroke = function (r, g, b, a) {
- const color = p5.prototype.color.apply(this._pInst, arguments);
- this.curStrokeColor = color._array;
-};
-
-p5.RendererGL.prototype.strokeCap = function (cap) {
- this.curStrokeCap = cap;
-};
-
-p5.RendererGL.prototype.strokeJoin = function (join) {
- this.curStrokeJoin = join;
-};
-
-p5.RendererGL.prototype.filter = function (filterType) {
- // filter can be achieved using custom shaders.
- // https://github.com/aferriss/p5jsShaderExamples
- // https://itp-xstory.github.io/p5js-shaders/#/
- p5._friendlyError('filter() does not work in WEBGL mode');
-};
-
-p5.RendererGL.prototype.blendMode = function (mode) {
- if (
- mode === constants.DARKEST ||
- mode === constants.LIGHTEST ||
- mode === constants.ADD ||
- mode === constants.BLEND ||
- mode === constants.SUBTRACT ||
- mode === constants.SCREEN ||
- mode === constants.EXCLUSION ||
- mode === constants.REPLACE ||
- mode === constants.MULTIPLY ||
- mode === constants.REMOVE
- )
- this.curBlendMode = mode;
- else if (
- mode === constants.BURN ||
- mode === constants.OVERLAY ||
- mode === constants.HARD_LIGHT ||
- mode === constants.SOFT_LIGHT ||
- mode === constants.DODGE
- ) {
- console.warn(
- 'BURN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, and DODGE only work for blendMode in 2D mode.'
- );
- }
-};
-
-p5.RendererGL.prototype.erase = function (opacityFill, opacityStroke) {
- if (!this._isErasing) {
- this._applyBlendMode(constants.REMOVE);
- this._isErasing = true;
-
- this._cachedFillStyle = this.curFillColor.slice();
- this.curFillColor = [1, 1, 1, opacityFill / 255];
-
- this._cachedStrokeStyle = this.curStrokeColor.slice();
- this.curStrokeColor = [1, 1, 1, opacityStroke / 255];
- }
-};
-
-p5.RendererGL.prototype.noErase = function () {
- if (this._isErasing) {
- this._isErasing = false;
- this.curFillColor = this._cachedFillStyle.slice();
- this.curStrokeColor = this._cachedStrokeStyle.slice();
- this.blendMode(this._cachedBlendMode);
- }
-};
-
-/**
- * Change weight of stroke
- * @method strokeWeight
- * @param {Number} stroke weight to be used for drawing
- * @example
- *
- *
- * function setup() {
- * createCanvas(200, 400, WEBGL);
- * setAttributes('antialias', true);
- * }
- *
- * function draw() {
- * background(0);
- * noStroke();
- * translate(0, -100, 0);
- * stroke(240, 150, 150);
- * fill(100, 100, 240);
- * push();
- * strokeWeight(8);
- * rotateX(frameCount * 0.01);
- * rotateY(frameCount * 0.01);
- * sphere(75);
- * pop();
- * push();
- * translate(0, 200, 0);
- * strokeWeight(1);
- * rotateX(frameCount * 0.01);
- * rotateY(frameCount * 0.01);
- * sphere(75);
- * pop();
- * }
- *
- *
- *
- * @alt
- * black canvas with two purple rotating spheres with pink
- * outlines the sphere on top has much heavier outlines,
- */
-p5.RendererGL.prototype.strokeWeight = function (w) {
- if (this.curStrokeWeight !== w) {
- this.pointSize = w;
- this.curStrokeWeight = w;
- }
-};
-
-// x,y are canvas-relative (pre-scaled by _pixelDensity)
-p5.RendererGL.prototype._getPixel = function (x, y) {
- const gl = this.GL;
- return readPixelWebGL(
- gl,
- null,
- x,
- y,
- gl.RGBA,
- gl.UNSIGNED_BYTE,
- this._pInst.height * this._pInst.pixelDensity()
- );
-};
-
-/**
- * Loads the pixels data for this canvas into the pixels[] attribute.
- * Note that updatePixels() and set() do not work.
- * Any pixel manipulation must be done directly to the pixels[] array.
- *
- * @private
- * @method loadPixels
- */
-
-p5.RendererGL.prototype.loadPixels = function () {
- const pixelsState = this._pixelsState;
-
- //@todo_FES
- if (this._pInst._glAttributes.preserveDrawingBuffer !== true) {
- console.log(
- 'loadPixels only works in WebGL when preserveDrawingBuffer ' + 'is true.'
- );
- return;
- }
-
- const pd = this._pInst._pixelDensity;
- const gl = this.GL;
-
- pixelsState._setProperty(
- 'pixels',
- readPixelsWebGL(
- pixelsState.pixels,
- gl,
- null,
- 0,
- 0,
- this.width * pd,
- this.height * pd,
- gl.RGBA,
- gl.UNSIGNED_BYTE,
- this.height * pd
- )
- );
-};
-
-p5.RendererGL.prototype.updatePixels = function () {
- const fbo = this._getTempFramebuffer();
- fbo.pixels = this._pixelsState.pixels;
- fbo.updatePixels();
- this._pInst.push();
- this._pInst.resetMatrix();
- this._pInst.clear();
- this._pInst.imageMode(constants.CENTER);
- this._pInst.image(fbo, 0, 0);
- this._pInst.pop();
- this.GL.clearDepth(1);
- this.GL.clear(this.GL.DEPTH_BUFFER_BIT);
-};
-
-/**
- * @private
- * @returns {p5.Framebuffer} A p5.Framebuffer set to match the size and settings
- * of the renderer's canvas. It will be created if it does not yet exist, and
- * reused if it does.
- */
-p5.RendererGL.prototype._getTempFramebuffer = function () {
- if (!this._tempFramebuffer) {
- this._tempFramebuffer = this._pInst.createFramebuffer({
- format: constants.UNSIGNED_BYTE,
- useDepth: this._pInst._glAttributes.depth,
- depthFormat: constants.UNSIGNED_INT,
- antialias: this._pInst._glAttributes.antialias
- });
- }
- return this._tempFramebuffer;
-};
-
/**
* @private
* @param {Uint8Array|Float32Array|undefined} pixels An existing pixels array to reuse if the size is the same
@@ -1063,52 +393,721 @@ export function readPixelWebGL(
return Array.from(pixels);
}
-
-//////////////////////////////////////////////
-// HASH | for geometry
-//////////////////////////////////////////////
-
-p5.RendererGL.prototype.geometryInHash = function (gId) {
- return this.retainedMode.geometry[gId] !== undefined;
-};
-
/**
- * [resize description]
+ * 3D graphics class
* @private
- * @param {Number} w [description]
- * @param {Number} h [description]
+ * @class p5.RendererGL
+ * @constructor
+ * @extends p5.Renderer
+ * @todo extend class to include public method for offscreen
+ * rendering (FBO).
*/
-p5.RendererGL.prototype.resize = function (w, h) {
- p5.Renderer.prototype.resize.call(this, w, h);
- this.GL.viewport(
- 0,
- 0,
- this.GL.drawingBufferWidth,
- this.GL.drawingBufferHeight
- );
- this._viewport = this.GL.getParameter(this.GL.VIEWPORT);
+p5.RendererGL = class RendererGL extends p5.Renderer {
+ constructor(elt, pInst, isMainCanvas, attr) {
+ super(elt, pInst, isMainCanvas);
+ this._setAttributeDefaults(pInst);
+ this._initContext();
+ this.isP3D = true; //lets us know we're in 3d mode
+
+ // This redundant property is useful in reminding you that you are
+ // interacting with WebGLRenderingContext, still worth considering future removal
+ this.GL = this.drawingContext;
+ this._pInst._setProperty('drawingContext', this.drawingContext);
+
+ // erasing
+ this._isErasing = false;
+
+ // lights
+ this._enableLighting = false;
+
+ this.ambientLightColors = [];
+ this.specularColors = [1, 1, 1];
+
+ this.directionalLightDirections = [];
+ this.directionalLightDiffuseColors = [];
+ this.directionalLightSpecularColors = [];
+
+ this.pointLightPositions = [];
+ this.pointLightDiffuseColors = [];
+ this.pointLightSpecularColors = [];
+
+ this.spotLightPositions = [];
+ this.spotLightDirections = [];
+ this.spotLightDiffuseColors = [];
+ this.spotLightSpecularColors = [];
+ this.spotLightAngle = [];
+ this.spotLightConc = [];
+
+ this.drawMode = constants.FILL;
+
+ this.curFillColor = this._cachedFillStyle = [1, 1, 1, 1];
+ this.curAmbientColor = this._cachedFillStyle = [1, 1, 1, 1];
+ this.curSpecularColor = this._cachedFillStyle = [0, 0, 0, 0];
+ this.curEmissiveColor = this._cachedFillStyle = [0, 0, 0, 0];
+ this.curStrokeColor = this._cachedStrokeStyle = [0, 0, 0, 1];
+
+ this.curBlendMode = constants.BLEND;
+ this._cachedBlendMode = undefined;
+ if (this.webglVersion === constants.WEBGL2) {
+ this.blendExt = this.GL;
+ } else {
+ this.blendExt = this.GL.getExtension('EXT_blend_minmax');
+ }
+ this._isBlending = false;
+
+
+ this._hasSetAmbient = false;
+ this._useSpecularMaterial = false;
+ this._useEmissiveMaterial = false;
+ this._useNormalMaterial = false;
+ this._useShininess = 1;
+
+ this._useLineColor = false;
+ this._useVertexColor = false;
+
+ this.registerEnabled = [];
+
+ this._tint = [255, 255, 255, 255];
+
+ // lightFalloff variables
+ this.constantAttenuation = 1;
+ this.linearAttenuation = 0;
+ this.quadraticAttenuation = 0;
+
+ /**
+ * model view, projection, & normal
+ * matrices
+ */
+ this.uMVMatrix = new p5.Matrix();
+ this.uPMatrix = new p5.Matrix();
+ this.uNMatrix = new p5.Matrix('mat3');
+
+ // Current vertex normal
+ this._currentNormal = new p5.Vector(0, 0, 1);
+
+ // Camera
+ this._curCamera = new p5.Camera(this);
+ this._curCamera._computeCameraDefaultSettings();
+ this._curCamera._setDefaultCamera();
+
+ // Information about the previous frame's touch object
+ // for executing orbitControl()
+ this.prevTouches = [];
+ // Velocity variable for use with orbitControl()
+ this.zoomVelocity = 0;
+ this.rotateVelocity = new p5.Vector(0, 0);
+ this.moveVelocity = new p5.Vector(0, 0);
+ // Flags for recording the state of zooming, rotation and moving
+ this.executeZoom = false;
+ this.executeRotateAndMove = false;
+
+ this._defaultLightShader = undefined;
+ this._defaultImmediateModeShader = undefined;
+ this._defaultNormalShader = undefined;
+ this._defaultColorShader = undefined;
+ this._defaultPointShader = undefined;
+
+ this.userFillShader = undefined;
+ this.userStrokeShader = undefined;
+ this.userPointShader = undefined;
+
+ // Default drawing is done in Retained Mode
+ // Geometry and Material hashes stored here
+ this.retainedMode = {
+ geometry: {},
+ buffers: {
+ stroke: [
+ new p5.RenderBuffer(4, 'lineVertexColors', 'lineColorBuffer', 'aVertexColor', this, this._flatten),
+ new p5.RenderBuffer(3, 'lineVertices', 'lineVerticesBuffer', 'aPosition', this, this._flatten),
+ new p5.RenderBuffer(3, 'lineTangentsIn', 'lineTangentsInBuffer', 'aTangentIn', this, this._flatten),
+ new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this, this._flatten),
+ new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this)
+ ],
+ fill: [
+ new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
+ new p5.RenderBuffer(3, 'vertexNormals', 'normalBuffer', 'aNormal', this, this._vToNArray),
+ new p5.RenderBuffer(4, 'vertexColors', 'colorBuffer', 'aVertexColor', this),
+ new p5.RenderBuffer(3, 'vertexAmbients', 'ambientBuffer', 'aAmbientColor', this),
+ //new BufferDef(3, 'vertexSpeculars', 'specularBuffer', 'aSpecularColor'),
+ new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
+ ],
+ text: [
+ new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
+ new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
+ ]
+ }
+ };
+
+ // Immediate Mode
+ // Geometry and Material hashes stored here
+ this.immediateMode = {
+ geometry: new p5.Geometry(),
+ shapeMode: constants.TRIANGLE_FAN,
+ _bezierVertex: [],
+ _quadraticVertex: [],
+ _curveVertex: [],
+ buffers: {
+ fill: [
+ new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
+ new p5.RenderBuffer(3, 'vertexNormals', 'normalBuffer', 'aNormal', this, this._vToNArray),
+ new p5.RenderBuffer(4, 'vertexColors', 'colorBuffer', 'aVertexColor', this),
+ new p5.RenderBuffer(3, 'vertexAmbients', 'ambientBuffer', 'aAmbientColor', this),
+ new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
+ ],
+ stroke: [
+ new p5.RenderBuffer(4, 'lineVertexColors', 'lineColorBuffer', 'aVertexColor', this, this._flatten),
+ new p5.RenderBuffer(3, 'lineVertices', 'lineVerticesBuffer', 'aPosition', this, this._flatten),
+ new p5.RenderBuffer(3, 'lineTangentsIn', 'lineTangentsInBuffer', 'aTangentIn', this, this._flatten),
+ new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this, this._flatten),
+ new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this)
+ ],
+ point: this.GL.createBuffer()
+ }
+ };
+
+ this.pointSize = 5.0; //default point size
+ this.curStrokeWeight = 1;
+ this.curStrokeCap = constants.ROUND;
+ this.curStrokeJoin = constants.ROUND;
+
+ // map of texture sources to textures created in this gl context via this.getTexture(src)
+ this.textures = new Map();
+
+ // set of framebuffers in use
+ this.framebuffers = new Set();
+
+ this.textureMode = constants.IMAGE;
+ // default wrap settings
+ this.textureWrapX = constants.CLAMP;
+ this.textureWrapY = constants.CLAMP;
+ this._tex = null;
+ this._curveTightness = 6;
+
+ // lookUpTable for coefficients needed to be calculated for bezierVertex, same are used for curveVertex
+ this._lookUpTableBezier = [];
+ // lookUpTable for coefficients needed to be calculated for quadraticVertex
+ this._lookUpTableQuadratic = [];
+
+ // current curveDetail in the Bezier lookUpTable
+ this._lutBezierDetail = 0;
+ // current curveDetail in the Quadratic lookUpTable
+ this._lutQuadraticDetail = 0;
+
+ // Used to distinguish between user calls to vertex() and internal calls
+ this.isProcessingVertices = false;
+ this._tessy = this._initTessy();
+
+ this.fontInfos = {};
+
+ this._curShader = undefined;
+
+ return this;
+ }
+
+ //////////////////////////////////////////////
+ // Setting
+ //////////////////////////////////////////////
+
+ _setAttributeDefaults(pInst) {
+ // See issue #3850, safer to enable AA in Safari
+ const applyAA = navigator.userAgent.toLowerCase().includes('safari');
+ const defaults = {
+ alpha: true,
+ depth: true,
+ stencil: true,
+ antialias: applyAA,
+ premultipliedAlpha: true,
+ preserveDrawingBuffer: true,
+ perPixelLighting: true,
+ version: 2
+ };
+ if (pInst._glAttributes === null) {
+ pInst._glAttributes = defaults;
+ } else {
+ pInst._glAttributes = Object.assign(defaults, pInst._glAttributes);
+ }
+ return;
+ }
+
+ _initContext() {
+ if (this._pInst._glAttributes.version !== 1) {
+ // Unless WebGL1 is explicitly asked for, try to create a WebGL2 context
+ this.drawingContext =
+ this.canvas.getContext('webgl2', this._pInst._glAttributes);
+ }
+ this.webglVersion =
+ this.drawingContext ? constants.WEBGL2 : constants.WEBGL;
+ // If this is the main canvas, make sure the global `webglVersion` is set
+ this._pInst._setProperty('webglVersion', this.webglVersion);
+ if (!this.drawingContext) {
+ // If we were unable to create a WebGL2 context (either because it was
+ // disabled via `setAttributes({ version: 1 })` or because the device
+ // doesn't support it), fall back to a WebGL1 context
+ this.drawingContext =
+ this.canvas.getContext('webgl', this._pInst._glAttributes) ||
+ this.canvas.getContext('experimental-webgl', this._pInst._glAttributes);
+ }
+ if (this.drawingContext === null) {
+ throw new Error('Error creating webgl context');
+ } else {
+ const gl = this.drawingContext;
+ gl.enable(gl.DEPTH_TEST);
+ gl.depthFunc(gl.LEQUAL);
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
+ // Make sure all images are loaded into the canvas premultiplied so that
+ // they match the way we render colors. This will make framebuffer textures
+ // be encoded the same way as textures from everything else.
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
+ this._viewport = this.drawingContext.getParameter(
+ this.drawingContext.VIEWPORT
+ );
+ }
+ }
+
+ //This is helper function to reset the context anytime the attributes
+ //are changed with setAttributes()
+
+ _resetContext(options, callback) {
+ const w = this.width;
+ const h = this.height;
+ const defaultId = this.canvas.id;
+ const isPGraphics = this._pInst instanceof p5.Graphics;
+
+ if (isPGraphics) {
+ const pg = this._pInst;
+ pg.canvas.parentNode.removeChild(pg.canvas);
+ pg.canvas = document.createElement('canvas');
+ const node = pg._pInst._userNode || document.body;
+ node.appendChild(pg.canvas);
+ p5.Element.call(pg, pg.canvas, pg._pInst);
+ pg.width = w;
+ pg.height = h;
+ } else {
+ let c = this.canvas;
+ if (c) {
+ c.parentNode.removeChild(c);
+ }
+ c = document.createElement('canvas');
+ c.id = defaultId;
+ if (this._pInst._userNode) {
+ this._pInst._userNode.appendChild(c);
+ } else {
+ document.body.appendChild(c);
+ }
+ this._pInst.canvas = c;
+ this.canvas = c;
+ }
+
+ const renderer = new p5.RendererGL(
+ this._pInst.canvas,
+ this._pInst,
+ !isPGraphics
+ );
+ this._pInst._setProperty('_renderer', renderer);
+ renderer.resize(w, h);
+ renderer._applyDefaults();
+
+ if (!isPGraphics) {
+ this._pInst._elements.push(renderer);
+ }
+
+ if (typeof callback === 'function') {
+ //setTimeout with 0 forces the task to the back of the queue, this ensures that
+ //we finish switching out the renderer
+ setTimeout(() => {
+ callback.apply(window._renderer, options);
+ }, 0);
+ }
+ }
+
+
+ /**
+ * @class p5.RendererGL
+ */
+ _update() {
+ // reset model view and apply initial camera transform
+ // (containing only look at info; no projection).
+ this.uMVMatrix.set(
+ this._curCamera.cameraMatrix.mat4[0],
+ this._curCamera.cameraMatrix.mat4[1],
+ this._curCamera.cameraMatrix.mat4[2],
+ this._curCamera.cameraMatrix.mat4[3],
+ this._curCamera.cameraMatrix.mat4[4],
+ this._curCamera.cameraMatrix.mat4[5],
+ this._curCamera.cameraMatrix.mat4[6],
+ this._curCamera.cameraMatrix.mat4[7],
+ this._curCamera.cameraMatrix.mat4[8],
+ this._curCamera.cameraMatrix.mat4[9],
+ this._curCamera.cameraMatrix.mat4[10],
+ this._curCamera.cameraMatrix.mat4[11],
+ this._curCamera.cameraMatrix.mat4[12],
+ this._curCamera.cameraMatrix.mat4[13],
+ this._curCamera.cameraMatrix.mat4[14],
+ this._curCamera.cameraMatrix.mat4[15]
+ );
+
+ // reset light data for new frame.
+
+ this.ambientLightColors.length = 0;
+ this.specularColors = [1, 1, 1];
+
+ this.directionalLightDirections.length = 0;
+ this.directionalLightDiffuseColors.length = 0;
+ this.directionalLightSpecularColors.length = 0;
+
+ this.pointLightPositions.length = 0;
+ this.pointLightDiffuseColors.length = 0;
+ this.pointLightSpecularColors.length = 0;
+
+ this.spotLightPositions.length = 0;
+ this.spotLightDirections.length = 0;
+ this.spotLightDiffuseColors.length = 0;
+ this.spotLightSpecularColors.length = 0;
+ this.spotLightAngle.length = 0;
+ this.spotLightConc.length = 0;
+
+ this._enableLighting = false;
+
+ //reset tint value for new frame
+ this._tint = [255, 255, 255, 255];
+
+ //Clear depth every frame
+ this.GL.clear(this.GL.DEPTH_BUFFER_BIT);
+ }
+
+ /**
+ * [background description]
+ */
+ background(...args) {
+ const _col = this._pInst.color(...args);
+ const _r = _col.levels[0] / 255;
+ const _g = _col.levels[1] / 255;
+ const _b = _col.levels[2] / 255;
+ const _a = _col.levels[3] / 255;
+ this.clear(_r, _g, _b, _a);
+ }
+
+ //////////////////////////////////////////////
+ // COLOR
+ //////////////////////////////////////////////
+ /**
+ * Basic fill material for geometry with a given color
+ * @method fill
+ * @class p5.RendererGL
+ * @param {Number|Number[]|String|p5.Color} v1 gray value,
+ * red or hue value (depending on the current color mode),
+ * or color Array, or CSS color string
+ * @param {Number} [v2] green or saturation value
+ * @param {Number} [v3] blue or brightness value
+ * @param {Number} [a] opacity
+ * @chainable
+ * @example
+ *
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * }
+ *
+ * function draw() {
+ * background(0);
+ * noStroke();
+ * fill(100, 100, 240);
+ * rotateX(frameCount * 0.01);
+ * rotateY(frameCount * 0.01);
+ * box(75, 75, 75);
+ * }
+ *
+ *
+ *
+ * @alt
+ * black canvas with purple cube spinning
+ */
+ fill(v1, v2, v3, a) {
+ //see material.js for more info on color blending in webgl
+ const color = p5.prototype.color.apply(this._pInst, arguments);
+ this.curFillColor = color._array;
+ this.drawMode = constants.FILL;
+ this._useNormalMaterial = false;
+ this._tex = null;
+ }
+
+ /**
+ * Basic stroke material for geometry with a given color
+ * @method stroke
+ * @param {Number|Number[]|String|p5.Color} v1 gray value,
+ * red or hue value (depending on the current color mode),
+ * or color Array, or CSS color string
+ * @param {Number} [v2] green or saturation value
+ * @param {Number} [v3] blue or brightness value
+ * @param {Number} [a] opacity
+ * @example
+ *
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * }
+ *
+ * function draw() {
+ * background(0);
+ * stroke(240, 150, 150);
+ * fill(100, 100, 240);
+ * rotateX(frameCount * 0.01);
+ * rotateY(frameCount * 0.01);
+ * box(75, 75, 75);
+ * }
+ *
+ *
+ *
+ * @alt
+ * black canvas with purple cube with pink outline spinning
+ */
+ stroke(r, g, b, a) {
+ const color = p5.prototype.color.apply(this._pInst, arguments);
+ this.curStrokeColor = color._array;
+ }
+
+ strokeCap(cap) {
+ this.curStrokeCap = cap;
+ }
+
+ strokeJoin(join) {
+ this.curStrokeJoin = join;
+ }
+
+ filter(filterType) {
+ // filter can be achieved using custom shaders.
+ // https://github.com/aferriss/p5jsShaderExamples
+ // https://itp-xstory.github.io/p5js-shaders/#/
+ p5._friendlyError('filter() does not work in WEBGL mode');
+ }
+ blendMode(mode) {
+ if (
+ mode === constants.DARKEST ||
+ mode === constants.LIGHTEST ||
+ mode === constants.ADD ||
+ mode === constants.BLEND ||
+ mode === constants.SUBTRACT ||
+ mode === constants.SCREEN ||
+ mode === constants.EXCLUSION ||
+ mode === constants.REPLACE ||
+ mode === constants.MULTIPLY ||
+ mode === constants.REMOVE
+ )
+ this.curBlendMode = mode;
+ else if (
+ mode === constants.BURN ||
+ mode === constants.OVERLAY ||
+ mode === constants.HARD_LIGHT ||
+ mode === constants.SOFT_LIGHT ||
+ mode === constants.DODGE
+ ) {
+ console.warn(
+ 'BURN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, and DODGE only work for blendMode in 2D mode.'
+ );
+ }
+ }
+
+ erase(opacityFill, opacityStroke) {
+ if (!this._isErasing) {
+ this._applyBlendMode(constants.REMOVE);
+ this._isErasing = true;
+
+ this._cachedFillStyle = this.curFillColor.slice();
+ this.curFillColor = [1, 1, 1, opacityFill / 255];
+
+ this._cachedStrokeStyle = this.curStrokeColor.slice();
+ this.curStrokeColor = [1, 1, 1, opacityStroke / 255];
+ }
+ }
+
+ noErase() {
+ if (this._isErasing) {
+ this._isErasing = false;
+ this.curFillColor = this._cachedFillStyle.slice();
+ this.curStrokeColor = this._cachedStrokeStyle.slice();
+ this.blendMode(this._cachedBlendMode);
+ }
+ }
+
+ /**
+ * Change weight of stroke
+ * @method strokeWeight
+ * @param {Number} stroke weight to be used for drawing
+ * @example
+ *
+ *
+ * function setup() {
+ * createCanvas(200, 400, WEBGL);
+ * setAttributes('antialias', true);
+ * }
+ *
+ * function draw() {
+ * background(0);
+ * noStroke();
+ * translate(0, -100, 0);
+ * stroke(240, 150, 150);
+ * fill(100, 100, 240);
+ * push();
+ * strokeWeight(8);
+ * rotateX(frameCount * 0.01);
+ * rotateY(frameCount * 0.01);
+ * sphere(75);
+ * pop();
+ * push();
+ * translate(0, 200, 0);
+ * strokeWeight(1);
+ * rotateX(frameCount * 0.01);
+ * rotateY(frameCount * 0.01);
+ * sphere(75);
+ * pop();
+ * }
+ *
+ *
+ *
+ * @alt
+ * black canvas with two purple rotating spheres with pink
+ * outlines the sphere on top has much heavier outlines,
+ */
+ strokeWeight(w) {
+ if (this.curStrokeWeight !== w) {
+ this.pointSize = w;
+ this.curStrokeWeight = w;
+ }
+ }
- this._curCamera._resize();
+ // x,y are canvas-relative (pre-scaled by _pixelDensity)
+ _getPixel(x, y) {
+ const gl = this.GL;
+ return readPixelWebGL(
+ gl,
+ null,
+ x,
+ y,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ this._pInst.height * this._pInst.pixelDensity()
+ );
+ }
+
+ /**
+ * Loads the pixels data for this canvas into the pixels[] attribute.
+ * Note that updatePixels() and set() do not work.
+ * Any pixel manipulation must be done directly to the pixels[] array.
+ *
+ * @private
+ * @method loadPixels
+ */
+
+ loadPixels() {
+ const pixelsState = this._pixelsState;
+
+ //@todo_FES
+ if (this._pInst._glAttributes.preserveDrawingBuffer !== true) {
+ console.log(
+ 'loadPixels only works in WebGL when preserveDrawingBuffer ' + 'is true.'
+ );
+ return;
+ }
+
+ const pd = this._pInst._pixelDensity;
+ const gl = this.GL;
- //resize pixels buffer
- const pixelsState = this._pixelsState;
- if (typeof pixelsState.pixels !== 'undefined') {
pixelsState._setProperty(
'pixels',
- new Uint8Array(
- this.GL.drawingBufferWidth * this.GL.drawingBufferHeight * 4
+ readPixelsWebGL(
+ pixelsState.pixels,
+ gl,
+ null,
+ 0,
+ 0,
+ this.width * pd,
+ this.height * pd,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ this.height * pd
)
);
}
- for (const framebuffer of this.framebuffers) {
- // Notify framebuffers of the resize so that any auto-sized framebuffers
- // can also update their size
- framebuffer._canvasSizeChanged();
+ updatePixels() {
+ const fbo = this._getTempFramebuffer();
+ fbo.pixels = this._pixelsState.pixels;
+ fbo.updatePixels();
+ this._pInst.push();
+ this._pInst.resetMatrix();
+ this._pInst.clear();
+ this._pInst.imageMode(constants.CENTER);
+ this._pInst.image(fbo, 0, 0);
+ this._pInst.pop();
+ this.GL.clearDepth(1);
+ this.GL.clear(this.GL.DEPTH_BUFFER_BIT);
}
-};
-/**
+ /**
+ * @private
+ * @returns {p5.Framebuffer} A p5.Framebuffer set to match the size and settings
+ * of the renderer's canvas. It will be created if it does not yet exist, and
+ * reused if it does.
+ */
+ _getTempFramebuffer() {
+ if (!this._tempFramebuffer) {
+ this._tempFramebuffer = this._pInst.createFramebuffer({
+ format: constants.UNSIGNED_BYTE,
+ useDepth: this._pInst._glAttributes.depth,
+ depthFormat: constants.UNSIGNED_INT,
+ antialias: this._pInst._glAttributes.antialias
+ });
+ }
+ return this._tempFramebuffer;
+ }
+
+
+
+ //////////////////////////////////////////////
+ // HASH | for geometry
+ //////////////////////////////////////////////
+
+ geometryInHash(gId) {
+ return this.retainedMode.geometry[gId] !== undefined;
+ }
+
+ /**
+ * [resize description]
+ * @private
+ * @param {Number} w [description]
+ * @param {Number} h [description]
+ */
+ resize(w, h) {
+ p5.Renderer.prototype.resize.call(this, w, h);
+ this.GL.viewport(
+ 0,
+ 0,
+ this.GL.drawingBufferWidth,
+ this.GL.drawingBufferHeight
+ );
+ this._viewport = this.GL.getParameter(this.GL.VIEWPORT);
+
+ this._curCamera._resize();
+
+ //resize pixels buffer
+ const pixelsState = this._pixelsState;
+ if (typeof pixelsState.pixels !== 'undefined') {
+ pixelsState._setProperty(
+ 'pixels',
+ new Uint8Array(
+ this.GL.drawingBufferWidth * this.GL.drawingBufferHeight * 4
+ )
+ );
+ }
+
+ for (const framebuffer of this.framebuffers) {
+ // Notify framebuffers of the resize so that any auto-sized framebuffers
+ // can also update their size
+ framebuffer._canvasSizeChanged();
+ }
+ }
+
+ /**
* clears color and depth buffers
* with r,g,b,a
* @private
@@ -1117,31 +1116,31 @@ p5.RendererGL.prototype.resize = function (w, h) {
* @param {Number} b normalized blue val.
* @param {Number} a normalized alpha val.
*/
-p5.RendererGL.prototype.clear = function (...args) {
- const _r = args[0] || 0;
- const _g = args[1] || 0;
- const _b = args[2] || 0;
- const _a = args[3] || 0;
-
- this.GL.clearColor(_r * _a, _g * _a, _b * _a, _a);
- this.GL.clearDepth(1);
- this.GL.clear(this.GL.COLOR_BUFFER_BIT | this.GL.DEPTH_BUFFER_BIT);
-};
+ clear(...args) {
+ const _r = args[0] || 0;
+ const _g = args[1] || 0;
+ const _b = args[2] || 0;
+ const _a = args[3] || 0;
+
+ this.GL.clearColor(_r * _a, _g * _a, _b * _a, _a);
+ this.GL.clearDepth(1);
+ this.GL.clear(this.GL.COLOR_BUFFER_BIT | this.GL.DEPTH_BUFFER_BIT);
+ }
-p5.RendererGL.prototype.applyMatrix = function (a, b, c, d, e, f) {
- if (arguments.length === 16) {
- p5.Matrix.prototype.apply.apply(this.uMVMatrix, arguments);
- } else {
- this.uMVMatrix.apply([
- a, b, 0, 0,
- c, d, 0, 0,
- 0, 0, 1, 0,
- e, f, 0, 1
- ]);
+ applyMatrix(a, b, c, d, e, f) {
+ if (arguments.length === 16) {
+ p5.Matrix.prototype.apply.apply(this.uMVMatrix, arguments);
+ } else {
+ this.uMVMatrix.apply([
+ a, b, 0, 0,
+ c, d, 0, 0,
+ 0, 0, 1, 0,
+ e, f, 0, 1
+ ]);
+ }
}
-};
-/**
+ /**
* [translate description]
* @private
* @param {Number} x [description]
@@ -1150,17 +1149,17 @@ p5.RendererGL.prototype.applyMatrix = function (a, b, c, d, e, f) {
* @chainable
* @todo implement handle for components or vector as args
*/
-p5.RendererGL.prototype.translate = function (x, y, z) {
- if (x instanceof p5.Vector) {
- z = x.z;
- y = x.y;
- x = x.x;
- }
- this.uMVMatrix.translate([x, y, z]);
- return this;
-};
+ translate(x, y, z) {
+ if (x instanceof p5.Vector) {
+ z = x.z;
+ y = x.y;
+ x = x.x;
+ }
+ this.uMVMatrix.translate([x, y, z]);
+ return this;
+ }
-/**
+ /**
* Scales the Model View Matrix by a vector
* @private
* @param {Number | p5.Vector | Array} x [description]
@@ -1168,676 +1167,672 @@ p5.RendererGL.prototype.translate = function (x, y, z) {
* @param {Number} [z] z-axis scalar
* @chainable
*/
-p5.RendererGL.prototype.scale = function (x, y, z) {
- this.uMVMatrix.scale(x, y, z);
- return this;
-};
-
-p5.RendererGL.prototype.rotate = function (rad, axis) {
- if (typeof axis === 'undefined') {
- return this.rotateZ(rad);
+ scale(x, y, z) {
+ this.uMVMatrix.scale(x, y, z);
+ return this;
}
- p5.Matrix.prototype.rotate.apply(this.uMVMatrix, arguments);
- return this;
-};
-p5.RendererGL.prototype.rotateX = function (rad) {
- this.rotate(rad, 1, 0, 0);
- return this;
-};
+ rotate(rad, axis) {
+ if (typeof axis === 'undefined') {
+ return this.rotateZ(rad);
+ }
+ p5.Matrix.prototype.rotate.apply(this.uMVMatrix, arguments);
+ return this;
+ }
-p5.RendererGL.prototype.rotateY = function (rad) {
- this.rotate(rad, 0, 1, 0);
- return this;
-};
+ rotateX(rad) {
+ this.rotate(rad, 1, 0, 0);
+ return this;
+ }
-p5.RendererGL.prototype.rotateZ = function (rad) {
- this.rotate(rad, 0, 0, 1);
- return this;
-};
+ rotateY(rad) {
+ this.rotate(rad, 0, 1, 0);
+ return this;
+ }
-p5.RendererGL.prototype.push = function () {
- // get the base renderer style
- const style = p5.Renderer.prototype.push.apply(this);
-
- // add webgl-specific style properties
- const properties = style.properties;
-
- properties.uMVMatrix = this.uMVMatrix.copy();
- properties.uPMatrix = this.uPMatrix.copy();
- properties._curCamera = this._curCamera;
-
- // make a copy of the current camera for the push state
- // this preserves any references stored using 'createCamera'
- this._curCamera = this._curCamera.copy();
-
- properties.ambientLightColors = this.ambientLightColors.slice();
- properties.specularColors = this.specularColors.slice();
-
- properties.directionalLightDirections =
- this.directionalLightDirections.slice();
- properties.directionalLightDiffuseColors =
- this.directionalLightDiffuseColors.slice();
- properties.directionalLightSpecularColors =
- this.directionalLightSpecularColors.slice();
-
- properties.pointLightPositions = this.pointLightPositions.slice();
- properties.pointLightDiffuseColors = this.pointLightDiffuseColors.slice();
- properties.pointLightSpecularColors = this.pointLightSpecularColors.slice();
-
- properties.spotLightPositions = this.spotLightPositions.slice();
- properties.spotLightDirections = this.spotLightDirections.slice();
- properties.spotLightDiffuseColors = this.spotLightDiffuseColors.slice();
- properties.spotLightSpecularColors = this.spotLightSpecularColors.slice();
- properties.spotLightAngle = this.spotLightAngle.slice();
- properties.spotLightConc = this.spotLightConc.slice();
-
- properties.userFillShader = this.userFillShader;
- properties.userStrokeShader = this.userStrokeShader;
- properties.userPointShader = this.userPointShader;
-
- properties.pointSize = this.pointSize;
- properties.curStrokeWeight = this.curStrokeWeight;
- properties.curStrokeColor = this.curStrokeColor;
- properties.curFillColor = this.curFillColor;
- properties.curAmbientColor = this.curAmbientColor;
- properties.curSpecularColor = this.curSpecularColor;
- properties.curEmissiveColor = this.curEmissiveColor;
-
- properties._hasSetAmbient = this._hasSetAmbient;
- properties._useSpecularMaterial = this._useSpecularMaterial;
- properties._useEmissiveMaterial = this._useEmissiveMaterial;
- properties._useShininess = this._useShininess;
-
- properties.constantAttenuation = this.constantAttenuation;
- properties.linearAttenuation = this.linearAttenuation;
- properties.quadraticAttenuation = this.quadraticAttenuation;
-
- properties._enableLighting = this._enableLighting;
- properties._useNormalMaterial = this._useNormalMaterial;
- properties._tex = this._tex;
- properties.drawMode = this.drawMode;
-
- properties._currentNormal = this._currentNormal;
- properties.curBlendMode = this.curBlendMode;
-
- return style;
-};
+ rotateZ(rad) {
+ this.rotate(rad, 0, 0, 1);
+ return this;
+ }
-p5.RendererGL.prototype.resetMatrix = function () {
- this.uMVMatrix.set(
- this._curCamera.cameraMatrix.mat4[0],
- this._curCamera.cameraMatrix.mat4[1],
- this._curCamera.cameraMatrix.mat4[2],
- this._curCamera.cameraMatrix.mat4[3],
- this._curCamera.cameraMatrix.mat4[4],
- this._curCamera.cameraMatrix.mat4[5],
- this._curCamera.cameraMatrix.mat4[6],
- this._curCamera.cameraMatrix.mat4[7],
- this._curCamera.cameraMatrix.mat4[8],
- this._curCamera.cameraMatrix.mat4[9],
- this._curCamera.cameraMatrix.mat4[10],
- this._curCamera.cameraMatrix.mat4[11],
- this._curCamera.cameraMatrix.mat4[12],
- this._curCamera.cameraMatrix.mat4[13],
- this._curCamera.cameraMatrix.mat4[14],
- this._curCamera.cameraMatrix.mat4[15]
- );
- return this;
-};
+ push() {
+ // get the base renderer style
+ const style = p5.Renderer.prototype.push.apply(this);
+
+ // add webgl-specific style properties
+ const properties = style.properties;
+
+ properties.uMVMatrix = this.uMVMatrix.copy();
+ properties.uPMatrix = this.uPMatrix.copy();
+ properties._curCamera = this._curCamera;
+
+ // make a copy of the current camera for the push state
+ // this preserves any references stored using 'createCamera'
+ this._curCamera = this._curCamera.copy();
+
+ properties.ambientLightColors = this.ambientLightColors.slice();
+ properties.specularColors = this.specularColors.slice();
+
+ properties.directionalLightDirections =
+ this.directionalLightDirections.slice();
+ properties.directionalLightDiffuseColors =
+ this.directionalLightDiffuseColors.slice();
+ properties.directionalLightSpecularColors =
+ this.directionalLightSpecularColors.slice();
+
+ properties.pointLightPositions = this.pointLightPositions.slice();
+ properties.pointLightDiffuseColors = this.pointLightDiffuseColors.slice();
+ properties.pointLightSpecularColors = this.pointLightSpecularColors.slice();
+
+ properties.spotLightPositions = this.spotLightPositions.slice();
+ properties.spotLightDirections = this.spotLightDirections.slice();
+ properties.spotLightDiffuseColors = this.spotLightDiffuseColors.slice();
+ properties.spotLightSpecularColors = this.spotLightSpecularColors.slice();
+ properties.spotLightAngle = this.spotLightAngle.slice();
+ properties.spotLightConc = this.spotLightConc.slice();
+
+ properties.userFillShader = this.userFillShader;
+ properties.userStrokeShader = this.userStrokeShader;
+ properties.userPointShader = this.userPointShader;
+
+ properties.pointSize = this.pointSize;
+ properties.curStrokeWeight = this.curStrokeWeight;
+ properties.curStrokeColor = this.curStrokeColor;
+ properties.curFillColor = this.curFillColor;
+ properties.curAmbientColor = this.curAmbientColor;
+ properties.curSpecularColor = this.curSpecularColor;
+ properties.curEmissiveColor = this.curEmissiveColor;
+
+ properties._hasSetAmbient = this._hasSetAmbient;
+ properties._useSpecularMaterial = this._useSpecularMaterial;
+ properties._useEmissiveMaterial = this._useEmissiveMaterial;
+ properties._useShininess = this._useShininess;
+
+ properties.constantAttenuation = this.constantAttenuation;
+ properties.linearAttenuation = this.linearAttenuation;
+ properties.quadraticAttenuation = this.quadraticAttenuation;
+
+ properties._enableLighting = this._enableLighting;
+ properties._useNormalMaterial = this._useNormalMaterial;
+ properties._tex = this._tex;
+ properties.drawMode = this.drawMode;
+
+ properties._currentNormal = this._currentNormal;
+ properties.curBlendMode = this.curBlendMode;
+
+ return style;
+ }
+ resetMatrix() {
+ this.uMVMatrix.set(
+ this._curCamera.cameraMatrix.mat4[0],
+ this._curCamera.cameraMatrix.mat4[1],
+ this._curCamera.cameraMatrix.mat4[2],
+ this._curCamera.cameraMatrix.mat4[3],
+ this._curCamera.cameraMatrix.mat4[4],
+ this._curCamera.cameraMatrix.mat4[5],
+ this._curCamera.cameraMatrix.mat4[6],
+ this._curCamera.cameraMatrix.mat4[7],
+ this._curCamera.cameraMatrix.mat4[8],
+ this._curCamera.cameraMatrix.mat4[9],
+ this._curCamera.cameraMatrix.mat4[10],
+ this._curCamera.cameraMatrix.mat4[11],
+ this._curCamera.cameraMatrix.mat4[12],
+ this._curCamera.cameraMatrix.mat4[13],
+ this._curCamera.cameraMatrix.mat4[14],
+ this._curCamera.cameraMatrix.mat4[15]
+ );
+ return this;
+ }
-//////////////////////////////////////////////
-// SHADER
-//////////////////////////////////////////////
+ //////////////////////////////////////////////
+ // SHADER
+ //////////////////////////////////////////////
-/*
+ /*
* shaders are created and cached on a per-renderer basis,
* on the grounds that each renderer will have its own gl context
* and the shader must be valid in that context.
*/
-p5.RendererGL.prototype._getImmediateStrokeShader = function () {
- // select the stroke shader to use
- const stroke = this.userStrokeShader;
- if (!stroke || !stroke.isStrokeShader()) {
- return this._getLineShader();
+ _getImmediateStrokeShader() {
+ // select the stroke shader to use
+ const stroke = this.userStrokeShader;
+ if (!stroke || !stroke.isStrokeShader()) {
+ return this._getLineShader();
+ }
+ return stroke;
}
- return stroke;
-};
-p5.RendererGL.prototype._getRetainedStrokeShader =
- p5.RendererGL.prototype._getImmediateStrokeShader;
-/*
- * selects which fill shader should be used based on renderer state,
- * for use with begin/endShape and immediate vertex mode.
- */
-p5.RendererGL.prototype._getImmediateFillShader = function () {
- const fill = this.userFillShader;
- if (this._useNormalMaterial) {
- if (!fill || !fill.isNormalShader()) {
- return this._getNormalShader();
- }
+ _getRetainedStrokeShader() {
+ return this._getImmediateStrokeShader();
}
- if (this._enableLighting) {
- if (!fill || !fill.isLightShader()) {
- return this._getLightShader();
+
+ /*
+ * selects which fill shader should be used based on renderer state,
+ * for use with begin/endShape and immediate vertex mode.
+ */
+ _getImmediateFillShader() {
+ const fill = this.userFillShader;
+ if (this._useNormalMaterial) {
+ if (!fill || !fill.isNormalShader()) {
+ return this._getNormalShader();
+ }
}
- } else if (this._tex) {
- if (!fill || !fill.isTextureShader()) {
- return this._getLightShader();
+ if (this._enableLighting) {
+ if (!fill || !fill.isLightShader()) {
+ return this._getLightShader();
+ }
+ } else if (this._tex) {
+ if (!fill || !fill.isTextureShader()) {
+ return this._getLightShader();
+ }
+ } else if (!fill /*|| !fill.isColorShader()*/) {
+ return this._getImmediateModeShader();
}
- } else if (!fill /*|| !fill.isColorShader()*/) {
- return this._getImmediateModeShader();
+ return fill;
}
- return fill;
-};
-/*
- * selects which fill shader should be used based on renderer state
- * for retained mode.
- */
-p5.RendererGL.prototype._getRetainedFillShader = function () {
- if (this._useNormalMaterial) {
- return this._getNormalShader();
- }
+ /*
+ * selects which fill shader should be used based on renderer state
+ * for retained mode.
+ */
+ _getRetainedFillShader() {
+ if (this._useNormalMaterial) {
+ return this._getNormalShader();
+ }
- const fill = this.userFillShader;
- if (this._enableLighting) {
- if (!fill || !fill.isLightShader()) {
- return this._getLightShader();
+ const fill = this.userFillShader;
+ if (this._enableLighting) {
+ if (!fill || !fill.isLightShader()) {
+ return this._getLightShader();
+ }
+ } else if (this._tex) {
+ if (!fill || !fill.isTextureShader()) {
+ return this._getLightShader();
+ }
+ } else if (!fill /* || !fill.isColorShader()*/) {
+ return this._getColorShader();
}
- } else if (this._tex) {
- if (!fill || !fill.isTextureShader()) {
- return this._getLightShader();
+ return fill;
+ }
+
+ _getImmediatePointShader() {
+ // select the point shader to use
+ const point = this.userPointShader;
+ if (!point || !point.isPointShader()) {
+ return this._getPointShader();
}
- } else if (!fill /* || !fill.isColorShader()*/) {
- return this._getColorShader();
+ return point;
}
- return fill;
-};
-p5.RendererGL.prototype._getImmediatePointShader = function () {
- // select the point shader to use
- const point = this.userPointShader;
- if (!point || !point.isPointShader()) {
- return this._getPointShader();
+ _getRetainedLineShader() {
+ return this._getImmediateLineShader();
}
- return point;
-};
-p5.RendererGL.prototype._getRetainedLineShader =
- p5.RendererGL.prototype._getImmediateLineShader;
+ _getLightShader() {
+ if (!this._defaultLightShader) {
+ if (this._pInst._glAttributes.perPixelLighting) {
+ this._defaultLightShader = new p5.Shader(
+ this,
+ defaultShaders.phongVert,
+ defaultShaders.phongFrag
+ );
+ } else {
+ this._defaultLightShader = new p5.Shader(
+ this,
+ defaultShaders.lightVert,
+ defaultShaders.lightTextureFrag
+ );
+ }
+ }
-p5.RendererGL.prototype._getLightShader = function () {
- if (!this._defaultLightShader) {
- if (this._pInst._glAttributes.perPixelLighting) {
- this._defaultLightShader = new p5.Shader(
- this,
- defaultShaders.phongVert,
- defaultShaders.phongFrag
- );
- } else {
- this._defaultLightShader = new p5.Shader(
+ return this._defaultLightShader;
+ }
+
+ _getImmediateModeShader() {
+ if (!this._defaultImmediateModeShader) {
+ this._defaultImmediateModeShader = new p5.Shader(
this,
- defaultShaders.lightVert,
- defaultShaders.lightTextureFrag
+ defaultShaders.immediateVert,
+ defaultShaders.vertexColorFrag
);
}
+
+ return this._defaultImmediateModeShader;
}
- return this._defaultLightShader;
-};
+ _getNormalShader() {
+ if (!this._defaultNormalShader) {
+ this._defaultNormalShader = new p5.Shader(
+ this,
+ defaultShaders.normalVert,
+ defaultShaders.normalFrag
+ );
+ }
-p5.RendererGL.prototype._getImmediateModeShader = function () {
- if (!this._defaultImmediateModeShader) {
- this._defaultImmediateModeShader = new p5.Shader(
- this,
- defaultShaders.immediateVert,
- defaultShaders.vertexColorFrag
- );
+ return this._defaultNormalShader;
}
- return this._defaultImmediateModeShader;
-};
+ _getColorShader() {
+ if (!this._defaultColorShader) {
+ this._defaultColorShader = new p5.Shader(
+ this,
+ defaultShaders.normalVert,
+ defaultShaders.basicFrag
+ );
+ }
-p5.RendererGL.prototype._getNormalShader = function () {
- if (!this._defaultNormalShader) {
- this._defaultNormalShader = new p5.Shader(
- this,
- defaultShaders.normalVert,
- defaultShaders.normalFrag
- );
+ return this._defaultColorShader;
}
- return this._defaultNormalShader;
-};
-
-p5.RendererGL.prototype._getColorShader = function () {
- if (!this._defaultColorShader) {
- this._defaultColorShader = new p5.Shader(
- this,
- defaultShaders.normalVert,
- defaultShaders.basicFrag
- );
+ _getPointShader() {
+ if (!this._defaultPointShader) {
+ this._defaultPointShader = new p5.Shader(
+ this,
+ defaultShaders.pointVert,
+ defaultShaders.pointFrag
+ );
+ }
+ return this._defaultPointShader;
}
- return this._defaultColorShader;
-};
+ _getLineShader() {
+ if (!this._defaultLineShader) {
+ this._defaultLineShader = new p5.Shader(
+ this,
+ defaultShaders.lineVert,
+ defaultShaders.lineFrag
+ );
+ }
-p5.RendererGL.prototype._getPointShader = function () {
- if (!this._defaultPointShader) {
- this._defaultPointShader = new p5.Shader(
- this,
- defaultShaders.pointVert,
- defaultShaders.pointFrag
- );
+ return this._defaultLineShader;
}
- return this._defaultPointShader;
-};
-p5.RendererGL.prototype._getLineShader = function () {
- if (!this._defaultLineShader) {
- this._defaultLineShader = new p5.Shader(
- this,
- defaultShaders.lineVert,
- defaultShaders.lineFrag
- );
+ _getFontShader() {
+ if (!this._defaultFontShader) {
+ if (this.webglVersion === constants.WEBGL) {
+ this.GL.getExtension('OES_standard_derivatives');
+ }
+ this._defaultFontShader = new p5.Shader(
+ this,
+ this._webGL2CompatibilityPrefix('vert', 'mediump') +
+ defaultShaders.fontVert,
+ this._webGL2CompatibilityPrefix('frag', 'mediump') +
+ defaultShaders.fontFrag
+ );
+ }
+ return this._defaultFontShader;
}
- return this._defaultLineShader;
-};
-
-p5.RendererGL.prototype._getFontShader = function () {
- if (!this._defaultFontShader) {
- if (this.webglVersion === constants.WEBGL) {
- this.GL.getExtension('OES_standard_derivatives');
+ _webGL2CompatibilityPrefix(
+ shaderType,
+ floatPrecision
+ ) {
+ let code = '';
+ if (this.webglVersion === constants.WEBGL2) {
+ code += '#version 300 es\n#define WEBGL2\n';
}
- this._defaultFontShader = new p5.Shader(
- this,
- this._webGL2CompatibilityPrefix('vert', 'mediump') +
- defaultShaders.fontVert,
- this._webGL2CompatibilityPrefix('frag', 'mediump') +
- defaultShaders.fontFrag
- );
+ if (shaderType === 'vert') {
+ code += '#define VERTEX_SHADER\n';
+ } else if (shaderType === 'frag') {
+ code += '#define FRAGMENT_SHADER\n';
+ }
+ if (floatPrecision) {
+ code += `precision ${floatPrecision} float;\n`;
+ }
+ return code;
}
- return this._defaultFontShader;
-};
-p5.RendererGL.prototype._webGL2CompatibilityPrefix = function (
- shaderType,
- floatPrecision
-) {
- let code = '';
- if (this.webglVersion === constants.WEBGL2) {
- code += '#version 300 es\n#define WEBGL2\n';
- }
- if (shaderType === 'vert') {
- code += '#define VERTEX_SHADER\n';
- } else if (shaderType === 'frag') {
- code += '#define FRAGMENT_SHADER\n';
- }
- if (floatPrecision) {
- code += `precision ${floatPrecision} float;\n`;
+ _getEmptyTexture() {
+ if (!this._emptyTexture) {
+ // a plain white texture RGBA, full alpha, single pixel.
+ const im = new p5.Image(1, 1);
+ im.set(0, 0, 255);
+ this._emptyTexture = new p5.Texture(this, im);
+ }
+ return this._emptyTexture;
}
- return code;
-};
-p5.RendererGL.prototype._getEmptyTexture = function () {
- if (!this._emptyTexture) {
- // a plain white texture RGBA, full alpha, single pixel.
- const im = new p5.Image(1, 1);
- im.set(0, 0, 255);
- this._emptyTexture = new p5.Texture(this, im);
- }
- return this._emptyTexture;
-};
+ getTexture(input) {
+ let src = input;
+ if (src instanceof p5.Framebuffer) {
+ src = src.color;
+ }
+
+ const texture = this.textures.get(src);
+ if (texture) {
+ return texture;
+ }
-p5.RendererGL.prototype.getTexture = function (input) {
- let src = input;
- if (src instanceof p5.Framebuffer) {
- src = src.color;
+ const tex = new p5.Texture(this, src);
+ this.textures.set(src, tex);
+ return tex;
}
- const texture = this.textures.get(src);
- if (texture) {
- return texture;
+ createFramebuffer(options) {
+ return new p5.Framebuffer(this, options);
}
- const tex = new p5.Texture(this, src);
- this.textures.set(src, tex);
- return tex;
-};
+ _setStrokeUniforms(strokeShader) {
+ strokeShader.bindShader();
-p5.RendererGL.prototype.createFramebuffer = function (options) {
- return new p5.Framebuffer(this, options);
-};
+ // set the uniform values
+ strokeShader.setUniform('uUseLineColor', this._useLineColor);
+ strokeShader.setUniform('uMaterialColor', this.curStrokeColor);
+ strokeShader.setUniform('uStrokeWeight', this.curStrokeWeight);
+ strokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]);
+ strokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]);
+ }
-p5.RendererGL.prototype._setStrokeUniforms = function (strokeShader) {
- strokeShader.bindShader();
+ _setFillUniforms(fillShader) {
+ fillShader.bindShader();
- // set the uniform values
- strokeShader.setUniform('uUseLineColor', this._useLineColor);
- strokeShader.setUniform('uMaterialColor', this.curStrokeColor);
- strokeShader.setUniform('uStrokeWeight', this.curStrokeWeight);
- strokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]);
- strokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]);
-};
-
-p5.RendererGL.prototype._setFillUniforms = function (fillShader) {
- fillShader.bindShader();
-
- // TODO: optimize
- fillShader.setUniform('uUseVertexColor', this._useVertexColor);
- fillShader.setUniform('uMaterialColor', this.curFillColor);
- fillShader.setUniform('isTexture', !!this._tex);
- if (this._tex) {
- fillShader.setUniform('uSampler', this._tex);
- }
- fillShader.setUniform('uTint', this._tint);
-
- fillShader.setUniform('uHasSetAmbient', this._hasSetAmbient);
- fillShader.setUniform('uAmbientMatColor', this.curAmbientColor);
- fillShader.setUniform('uSpecularMatColor', this.curSpecularColor);
- fillShader.setUniform('uEmissiveMatColor', this.curEmissiveColor);
- fillShader.setUniform('uSpecular', this._useSpecularMaterial);
- fillShader.setUniform('uEmissive', this._useEmissiveMaterial);
- fillShader.setUniform('uShininess', this._useShininess);
-
- fillShader.setUniform('uUseLighting', this._enableLighting);
-
- const pointLightCount = this.pointLightDiffuseColors.length / 3;
- fillShader.setUniform('uPointLightCount', pointLightCount);
- fillShader.setUniform('uPointLightLocation', this.pointLightPositions);
- fillShader.setUniform(
- 'uPointLightDiffuseColors',
- this.pointLightDiffuseColors
- );
- fillShader.setUniform(
- 'uPointLightSpecularColors',
- this.pointLightSpecularColors
- );
+ // TODO: optimize
+ fillShader.setUniform('uUseVertexColor', this._useVertexColor);
+ fillShader.setUniform('uMaterialColor', this.curFillColor);
+ fillShader.setUniform('isTexture', !!this._tex);
+ if (this._tex) {
+ fillShader.setUniform('uSampler', this._tex);
+ }
+ fillShader.setUniform('uTint', this._tint);
+
+ fillShader.setUniform('uHasSetAmbient', this._hasSetAmbient);
+ fillShader.setUniform('uAmbientMatColor', this.curAmbientColor);
+ fillShader.setUniform('uSpecularMatColor', this.curSpecularColor);
+ fillShader.setUniform('uEmissiveMatColor', this.curEmissiveColor);
+ fillShader.setUniform('uSpecular', this._useSpecularMaterial);
+ fillShader.setUniform('uEmissive', this._useEmissiveMaterial);
+ fillShader.setUniform('uShininess', this._useShininess);
+
+ fillShader.setUniform('uUseLighting', this._enableLighting);
+
+ const pointLightCount = this.pointLightDiffuseColors.length / 3;
+ fillShader.setUniform('uPointLightCount', pointLightCount);
+ fillShader.setUniform('uPointLightLocation', this.pointLightPositions);
+ fillShader.setUniform(
+ 'uPointLightDiffuseColors',
+ this.pointLightDiffuseColors
+ );
+ fillShader.setUniform(
+ 'uPointLightSpecularColors',
+ this.pointLightSpecularColors
+ );
- const directionalLightCount = this.directionalLightDiffuseColors.length / 3;
- fillShader.setUniform('uDirectionalLightCount', directionalLightCount);
- fillShader.setUniform('uLightingDirection', this.directionalLightDirections);
- fillShader.setUniform(
- 'uDirectionalDiffuseColors',
- this.directionalLightDiffuseColors
- );
- fillShader.setUniform(
- 'uDirectionalSpecularColors',
- this.directionalLightSpecularColors
- );
+ const directionalLightCount = this.directionalLightDiffuseColors.length / 3;
+ fillShader.setUniform('uDirectionalLightCount', directionalLightCount);
+ fillShader.setUniform('uLightingDirection', this.directionalLightDirections);
+ fillShader.setUniform(
+ 'uDirectionalDiffuseColors',
+ this.directionalLightDiffuseColors
+ );
+ fillShader.setUniform(
+ 'uDirectionalSpecularColors',
+ this.directionalLightSpecularColors
+ );
- // TODO: sum these here...
- const ambientLightCount = this.ambientLightColors.length / 3;
- fillShader.setUniform('uAmbientLightCount', ambientLightCount);
- fillShader.setUniform('uAmbientColor', this.ambientLightColors);
-
- const spotLightCount = this.spotLightDiffuseColors.length / 3;
- fillShader.setUniform('uSpotLightCount', spotLightCount);
- fillShader.setUniform('uSpotLightAngle', this.spotLightAngle);
- fillShader.setUniform('uSpotLightConc', this.spotLightConc);
- fillShader.setUniform('uSpotLightDiffuseColors', this.spotLightDiffuseColors);
- fillShader.setUniform(
- 'uSpotLightSpecularColors',
- this.spotLightSpecularColors
- );
- fillShader.setUniform('uSpotLightLocation', this.spotLightPositions);
- fillShader.setUniform('uSpotLightDirection', this.spotLightDirections);
+ // TODO: sum these here...
+ const ambientLightCount = this.ambientLightColors.length / 3;
+ fillShader.setUniform('uAmbientLightCount', ambientLightCount);
+ fillShader.setUniform('uAmbientColor', this.ambientLightColors);
+
+ const spotLightCount = this.spotLightDiffuseColors.length / 3;
+ fillShader.setUniform('uSpotLightCount', spotLightCount);
+ fillShader.setUniform('uSpotLightAngle', this.spotLightAngle);
+ fillShader.setUniform('uSpotLightConc', this.spotLightConc);
+ fillShader.setUniform('uSpotLightDiffuseColors', this.spotLightDiffuseColors);
+ fillShader.setUniform(
+ 'uSpotLightSpecularColors',
+ this.spotLightSpecularColors
+ );
+ fillShader.setUniform('uSpotLightLocation', this.spotLightPositions);
+ fillShader.setUniform('uSpotLightDirection', this.spotLightDirections);
- fillShader.setUniform('uConstantAttenuation', this.constantAttenuation);
- fillShader.setUniform('uLinearAttenuation', this.linearAttenuation);
- fillShader.setUniform('uQuadraticAttenuation', this.quadraticAttenuation);
+ fillShader.setUniform('uConstantAttenuation', this.constantAttenuation);
+ fillShader.setUniform('uLinearAttenuation', this.linearAttenuation);
+ fillShader.setUniform('uQuadraticAttenuation', this.quadraticAttenuation);
- fillShader.bindTextures();
-};
+ fillShader.bindTextures();
+ }
-p5.RendererGL.prototype._setPointUniforms = function (pointShader) {
- pointShader.bindShader();
+ _setPointUniforms(pointShader) {
+ pointShader.bindShader();
- // set the uniform values
- pointShader.setUniform('uMaterialColor', this.curStrokeColor);
- // @todo is there an instance where this isn't stroke weight?
- // should be they be same var?
- pointShader.setUniform(
- 'uPointSize',
- this.pointSize * this._pInst._pixelDensity
- );
-};
+ // set the uniform values
+ pointShader.setUniform('uMaterialColor', this.curStrokeColor);
+ // @todo is there an instance where this isn't stroke weight?
+ // should be they be same var?
+ pointShader.setUniform(
+ 'uPointSize',
+ this.pointSize * this._pInst._pixelDensity
+ );
+ }
-/* Binds a buffer to the drawing context
- * when passed more than two arguments it also updates or initializes
- * the data associated with the buffer
- */
-p5.RendererGL.prototype._bindBuffer = function (
- buffer,
- target,
- values,
- type,
- usage
-) {
- if (!target) target = this.GL.ARRAY_BUFFER;
- this.GL.bindBuffer(target, buffer);
- if (values !== undefined) {
- const data = new (type || Float32Array)(values);
- this.GL.bufferData(target, data, usage || this.GL.STATIC_DRAW);
+ /* Binds a buffer to the drawing context
+ * when passed more than two arguments it also updates or initializes
+ * the data associated with the buffer
+ */
+ _bindBuffer(
+ buffer,
+ target,
+ values,
+ type,
+ usage
+ ) {
+ if (!target) target = this.GL.ARRAY_BUFFER;
+ this.GL.bindBuffer(target, buffer);
+ if (values !== undefined) {
+ const data = new (type || Float32Array)(values);
+ this.GL.bufferData(target, data, usage || this.GL.STATIC_DRAW);
+ }
}
-};
-///////////////////////////////
-//// UTILITY FUNCTIONS
-//////////////////////////////
-p5.RendererGL.prototype._arraysEqual = function (a, b) {
- const aLength = a.length;
- if (aLength !== b.length) return false;
- for (let i = 0; i < aLength; i++) {
- if (a[i] !== b[i]) return false;
+ ///////////////////////////////
+ //// UTILITY FUNCTIONS
+ //////////////////////////////
+ _arraysEqual(a, b) {
+ const aLength = a.length;
+ if (aLength !== b.length) return false;
+ return a.every((ai,i) => ai === b[i]);
}
- return true;
-};
-p5.RendererGL.prototype._isTypedArray = function (arr) {
- let res = false;
- res = arr instanceof Float32Array;
- res = arr instanceof Float64Array;
- res = arr instanceof Int16Array;
- res = arr instanceof Uint16Array;
- res = arr instanceof Uint32Array;
- return res;
-};
-/**
- * turn a two dimensional array into one dimensional array
- * @private
- * @param {Array} arr 2-dimensional array
- * @return {Array} 1-dimensional array
- * [[1, 2, 3],[4, 5, 6]] -> [1, 2, 3, 4, 5, 6]
- */
-p5.RendererGL.prototype._flatten = function (arr) {
- //when empty, return empty
- if (arr.length === 0) {
- return [];
- } else if (arr.length > 20000) {
- //big models , load slower to avoid stack overflow
- //faster non-recursive flatten via axelduch
- //stackoverflow.com/questions/27266550/how-to-flatten-nested-array-in-javascript
- const toString = Object.prototype.toString;
- const arrayTypeStr = '[object Array]';
- const result = [];
- const nodes = arr.slice();
- let node;
- node = nodes.pop();
- do {
- if (toString.call(node) === arrayTypeStr) {
- nodes.push(...node);
- } else {
- result.push(node);
- }
- } while (nodes.length && (node = nodes.pop()) !== undefined);
- result.reverse(); // we reverse result to restore the original order
- return result;
- } else {
- //otherwise if model within limits for browser
- //use faster recursive loading
- return [].concat(...arr);
+ _isTypedArray(arr) {
+ return [
+ Float32Array,
+ Float64Array,
+ Int16Array,
+ Uint16Array,
+ Uint32Array
+ ].some(x => arr instanceof x);
+ }
+ /**
+ * turn a two dimensional array into one dimensional array
+ * @private
+ * @param {Array} arr 2-dimensional array
+ * @return {Array} 1-dimensional array
+ * [[1, 2, 3],[4, 5, 6]] -> [1, 2, 3, 4, 5, 6]
+ */
+ _flatten(arr) {
+ //when empty, return empty
+ if (arr.length === 0) {
+ return [];
+ } else if (arr.length > 20000) {
+ //big models , load slower to avoid stack overflow
+ //faster non-recursive flatten via axelduch
+ //stackoverflow.com/questions/27266550/how-to-flatten-nested-array-in-javascript
+ const result = [];
+ const nodes = arr.slice();
+ let node;
+ node = nodes.pop();
+ do {
+ if (Array.isArray(node)) {
+ nodes.push(...node);
+ } else {
+ result.push(node);
+ }
+ } while (nodes.length && (node = nodes.pop()) !== undefined);
+ result.reverse(); // we reverse result to restore the original order
+ return result;
+ } else {
+ //otherwise if model within limits for browser
+ //use faster recursive loading
+ return [].concat(...arr);
+ }
}
-};
-/**
- * turn a p5.Vector Array into a one dimensional number array
- * @private
- * @param {p5.Vector[]} arr an array of p5.Vector
- * @return {Number[]} a one dimensional array of numbers
- * [p5.Vector(1, 2, 3), p5.Vector(4, 5, 6)] ->
- * [1, 2, 3, 4, 5, 6]
- */
-p5.RendererGL.prototype._vToNArray = function (arr) {
- const ret = [];
+ /**
+ * turn a p5.Vector Array into a one dimensional number array
+ * @private
+ * @param {p5.Vector[]} arr an array of p5.Vector
+ * @return {Number[]} a one dimensional array of numbers
+ * [p5.Vector(1, 2, 3), p5.Vector(4, 5, 6)] ->
+ * [1, 2, 3, 4, 5, 6]
+ */
+ _vToNArray(arr) {
+ const ret = [];
+
+ for (const item of arr) {
+ ret.push(item.x, item.y, item.z);
+ }
- for (const item of arr) {
- ret.push(item.x, item.y, item.z);
+ return ret;
}
- return ret;
-};
-
-/**
- * ensures that p5 is using a 3d renderer. throws an error if not.
- */
-p5.prototype._assert3d = function (name) {
- if (!this._renderer.isP3D)
- throw new Error(
- `${name}() is only supported in WEBGL mode. If you'd like to use 3D graphics and WebGL, see https://p5js.org/examples/form-3d-primitives.html for more information.`
- );
-};
+ // function to calculate BezierVertex Coefficients
+ _bezierCoefficients (t) {
+ const t2 = t * t;
+ const t3 = t2 * t;
+ const mt = 1 - t;
+ const mt2 = mt * mt;
+ const mt3 = mt2 * mt;
+ return [mt3, 3 * mt2 * t, 3 * mt * t2, t3];
+ }
-// function to initialize GLU Tesselator
+ // function to calculate QuadraticVertex Coefficients
+ _quadraticCoefficients (t) {
+ const t2 = t * t;
+ const mt = 1 - t;
+ const mt2 = mt * mt;
+ return [mt2, 2 * mt * t, t2];
+ }
-p5.RendererGL.prototype.tessyVertexSize = 12;
-p5.RendererGL.prototype._initTessy = function initTesselator() {
- // function called for each vertex of tesselator output
- function vertexCallback(data, polyVertArray) {
- for (let i = 0; i < data.length; i++) {
- polyVertArray[polyVertArray.length] = data[i];
- }
+ // function to convert Bezier coordinates to Catmull Rom Splines
+ _bezierToCatmull (w) {
+ const p1 = w[1];
+ const p2 = w[1] + (w[2] - w[0]) / this._curveTightness;
+ const p3 = w[2] - (w[3] - w[1]) / this._curveTightness;
+ const p4 = w[2];
+ const p = [p1, p2, p3, p4];
+ return p;
}
+ _initTessy () {
+ // function called for each vertex of tesselator output
+ function vertexCallback(data, polyVertArray) {
+ for (let i = 0; i < data.length; i++) {
+ polyVertArray.push(data[i]);
+ }
+ }
- function begincallback(type) {
- if (type !== libtess.primitiveType.GL_TRIANGLES) {
- console.log(`expected TRIANGLES but got type: ${type}`);
+ function begincallback(type) {
+ if (type !== libtess.primitiveType.GL_TRIANGLES) {
+ console.log(`expected TRIANGLES but got type: ${type}`);
+ }
}
- }
- function errorcallback(errno) {
- console.log('error callback');
- console.log(`error number: ${errno}`);
- }
- // callback for when segments intersect and must be split
- function combinecallback(coords, data, weight) {
- const result = new Array(p5.RendererGL.prototype.tessyVertexSize).fill(0);
- for (let i = 0; i < weight.length; i++) {
- for (let j = 0; j < result.length; j++) {
- if (weight[i] === 0 || !data[i]) continue;
- result[j] += data[i][j] * weight[i];
+ function errorcallback(errno) {
+ console.log('error callback');
+ console.log(`error number: ${errno}`);
+ }
+ // callback for when segments intersect and must be split
+ function combinecallback(coords, data, weight) {
+ const result = new Array(p5.RendererGL.prototype.tessyVertexSize).fill(0);
+ for (let i = 0; i < weight.length; i++) {
+ for (let j = 0; j < result.length; j++) {
+ if (weight[i] === 0 || !data[i]) continue;
+ result[j] += data[i][j] * weight[i];
+ }
}
+ return result;
}
- return result;
- }
- function edgeCallback(flag) {
- // don't really care about the flag, but need no-strip/no-fan behavior
- }
+ function edgeCallback(flag) {
+ // don't really care about the flag, but need no-strip/no-fan behavior
+ }
- const tessy = new libtess.GluTesselator();
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, vertexCallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, begincallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback);
- tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback);
+ const tessy = new libtess.GluTesselator();
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, vertexCallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, begincallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback);
+ tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback);
- return tessy;
-};
+ return tessy;
+ }
-p5.RendererGL.prototype._triangulate = function (contours) {
- // libtess will take 3d verts and flatten to a plane for tesselation.
- // libtess is capable of calculating a plane to tesselate on, but
- // if all of the vertices have the same z values, we'll just
- // assume the face is facing the camera, letting us skip any performance
- // issues or bugs in libtess's automatic calculation.
- const z = contours[0] ? contours[0][2] : undefined;
- let allSameZ = true;
- for (const contour of contours) {
- for (
- let j = 0;
- j < contour.length;
- j += p5.RendererGL.prototype.tessyVertexSize
- ) {
- if (contour[j + 2] !== z) {
- allSameZ = false;
- break;
+ _triangulate (contours) {
+ // libtess will take 3d verts and flatten to a plane for tesselation.
+ // libtess is capable of calculating a plane to tesselate on, but
+ // if all of the vertices have the same z values, we'll just
+ // assume the face is facing the camera, letting us skip any performance
+ // issues or bugs in libtess's automatic calculation.
+ const z = contours[0] ? contours[0][2] : undefined;
+ let allSameZ = true;
+ for (const contour of contours) {
+ for (
+ let j = 0;
+ j < contour.length;
+ j += p5.RendererGL.prototype.tessyVertexSize
+ ) {
+ if (contour[j + 2] !== z) {
+ allSameZ = false;
+ break;
+ }
}
}
- }
- if (allSameZ) {
- this._tessy.gluTessNormal(0, 0, 1);
- } else {
- // Let libtess pick a plane for us
- this._tessy.gluTessNormal(0, 0, 0);
- }
-
- const triangleVerts = [];
- this._tessy.gluTessBeginPolygon(triangleVerts);
+ if (allSameZ) {
+ this._tessy.gluTessNormal(0, 0, 1);
+ } else {
+ // Let libtess pick a plane for us
+ this._tessy.gluTessNormal(0, 0, 0);
+ }
- for (let i = 0; i < contours.length; i++) {
- this._tessy.gluTessBeginContour();
- const contour = contours[i];
- for (
- let j = 0;
- j < contour.length;
- j += p5.RendererGL.prototype.tessyVertexSize
- ) {
- const coords = contour.slice(
- j,
- j + p5.RendererGL.prototype.tessyVertexSize
- );
- this._tessy.gluTessVertex(coords, coords);
+ const triangleVerts = [];
+ this._tessy.gluTessBeginPolygon(triangleVerts);
+
+ for(const contour of contours){
+ this._tessy.gluTessBeginContour();
+ for (
+ let j = 0;
+ j < contour.length;
+ j += p5.RendererGL.prototype.tessyVertexSize
+ ) {
+ const coords = contour.slice(
+ j,
+ j + p5.RendererGL.prototype.tessyVertexSize
+ );
+ this._tessy.gluTessVertex(coords, coords);
+ }
+ this._tessy.gluTessEndContour();
}
- this._tessy.gluTessEndContour();
- }
- // finish polygon
- this._tessy.gluTessEndPolygon();
+ // finish polygon
+ this._tessy.gluTessEndPolygon();
- return triangleVerts;
+ return triangleVerts;
+ }
};
-
-// function to calculate BezierVertex Coefficients
-p5.RendererGL.prototype._bezierCoefficients = function (t) {
- const t2 = t * t;
- const t3 = t2 * t;
- const mt = 1 - t;
- const mt2 = mt * mt;
- const mt3 = mt2 * mt;
- return [mt3, 3 * mt2 * t, 3 * mt * t2, t3];
+/**
+ * ensures that p5 is using a 3d renderer. throws an error if not.
+ */
+p5.prototype._assert3d = function (name) {
+ if (!this._renderer.isP3D)
+ throw new Error(
+ `${name}() is only supported in WEBGL mode. If you'd like to use 3D graphics and WebGL, see https://p5js.org/examples/form-3d-primitives.html for more information.`
+ );
};
-// function to calculate QuadraticVertex Coefficients
-p5.RendererGL.prototype._quadraticCoefficients = function (t) {
- const t2 = t * t;
- const mt = 1 - t;
- const mt2 = mt * mt;
- return [mt2, 2 * mt * t, t2];
-};
+// function to initialize GLU Tesselator
-// function to convert Bezier coordinates to Catmull Rom Splines
-p5.RendererGL.prototype._bezierToCatmull = function (w) {
- const p1 = w[1];
- const p2 = w[1] + (w[2] - w[0]) / this._curveTightness;
- const p3 = w[2] - (w[3] - w[1]) / this._curveTightness;
- const p4 = w[2];
- const p = [p1, p2, p3, p4];
- return p;
-};
+p5.RendererGL.prototype.tessyVertexSize = 12;
export default p5.RendererGL;
diff --git a/src/webgl/p5.Texture.js b/src/webgl/p5.Texture.js
index a1c139b6d9..f9dbe31439 100644
--- a/src/webgl/p5.Texture.js
+++ b/src/webgl/p5.Texture.js
@@ -452,15 +452,15 @@ p5.Texture = class Texture {
}
};
-export function checkWebGLCapabilities(renderer) {
- const gl = renderer.GL;
- const supportsFloat = renderer.webglVersion === constants.WEBGL2
+export function checkWebGLCapabilities({ GL, webglVersion }) {
+ const gl = GL;
+ const supportsFloat = webglVersion === constants.WEBGL2
? (gl.getExtension('EXT_color_buffer_float') &&
gl.getExtension('EXT_float_blend'))
: gl.getExtension('OES_texture_float');
const supportsFloatLinear = supportsFloat &&
gl.getExtension('OES_texture_float_linear');
- const supportsHalfFloat = renderer.webglVersion === constants.WEBGL2
+ const supportsHalfFloat = webglVersion === constants.WEBGL2
? gl.getExtension('EXT_color_buffer_float')
: gl.getExtension('OES_texture_half_float');
const supportsHalfFloatLinear = supportsHalfFloat &&