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 &&