p5
Version:
[](https://www.npmjs.com/package/p5)
1,549 lines (1,476 loc) • 810 kB
JavaScript
import { e as CORNER, i as CORNERS, C as CENTER, ap as COVER, aq as CONTAIN, R as RIGHT, ar as BOTTOM, u as BLEND, as as FILL, a1 as IMAGE, at as CLAMP, z as WEBGL2, j as ROUND, o as POINTS, L as LINES, p as TRIANGLES, a0 as WEBGL, $ as BLUR, a6 as DARKEST, a7 as LIGHTEST, ai as ADD, a5 as SUBTRACT, ab as SCREEN, aa as EXCLUSION, ac as REPLACE, a9 as MULTIPLY, a4 as REMOVE, ah as BURN, ad as OVERLAY, ae as HARD_LIGHT, af as SOFT_LIGHT, ag as DODGE, au as UNSIGNED_INT, av as UNSIGNED_BYTE, Z as THRESHOLD, _ as INVERT, K as OPAQUE, U as POSTERIZE, V as DILATE, Y as ERODE, X as GRAY, v as constants, aw as SIMPLE, ax as FULL, f as TWO_PI, O as OPEN, N as NORMAL, n as CLOSE, aj as PIE, ak as CHORD, T as TEXTURE, P as P2D, ay as NEAREST, az as REPEAT, aA as MIRROR, aB as FLOAT, aC as LINEAR, aD as HALF_FLOAT } from './constants-BRcElHU3.js';
import { C as Color, c as creatingReading, h as RGBA, R as RGB } from './creating_reading-Cr8L2Jnm.js';
import { Element } from './dom/p5.Element.js';
import { R as Renderer, I as Image } from './p5.Renderer-R23xoC7s.js';
import './dom/p5.MediaElement.js';
import primitives from './shape/2d_primitives.js';
import attributes from './shape/attributes.js';
import curves from './shape/curves.js';
import vertex from './shape/vertex.js';
import setting from './color/setting.js';
import * as omggif from 'omggif';
import canvas from './core/helpers.js';
import { parse } from './io/csv.js';
import { _checkFileExtension, downloadFile } from './io/utilities.js';
import { GIFEncoder, quantize, nearestColorIndex } from 'gifenc';
import pixels from './image/pixels.js';
import transform from './core/transform.js';
import GeometryBuilder from './webgl/GeometryBuilder.js';
import './math/p5.Matrix.js';
import { Vector } from './math/p5.Vector.js';
import { Quat } from './webgl/p5.Quat.js';
import { Matrix } from './math/Matrices/Matrix.js';
import { RenderBuffer } from './webgl/p5.RenderBuffer.js';
import { DataArray } from './webgl/p5.DataArray.js';
import { ShapeBuilder } from './webgl/ShapeBuilder.js';
import { GeometryBufferCache } from './webgl/GeometryBufferCache.js';
import { filterParamDefaults } from './image/const.js';
import customShapes, { PrimitiveToVerticesConverter } from './shape/custom_shapes.js';
import { Geometry } from './webgl/p5.Geometry.js';
import trigonometry from './math/trigonometry.js';
/**
* @module Image
* @submodule Image
* @for p5
* @requires core
*/
function image(p5, fn){
/**
* Creates a new <a href="#/p5.Image">p5.Image</a> object.
*
* `createImage()` uses the `width` and `height` parameters to set the new
* <a href="#/p5.Image">p5.Image</a> object's dimensions in pixels. The new
* <a href="#/p5.Image">p5.Image</a> can be modified by updating its
* <a href="#/p5.Image/pixels">pixels</a> array or by calling its
* <a href="#/p5.Image/get">get()</a> and
* <a href="#/p5.Image/set">set()</a> methods. The
* <a href="#/p5.Image/loadPixels">loadPixels()</a> method must be called
* before reading or modifying pixel values. The
* <a href="#/p5.Image/updatePixels">updatePixels()</a> method must be called
* for updates to take effect.
*
* Note: The new <a href="#/p5.Image">p5.Image</a> object is transparent by
* default.
*
* @method createImage
* @param {Integer} width width in pixels.
* @param {Integer} height height in pixels.
* @return {p5.Image} new <a href="#/p5.Image">p5.Image</a> object.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Image object.
* let img = createImage(66, 66);
*
* // Load the image's pixels into memory.
* img.loadPixels();
*
* // Set all the image's pixels to black.
* for (let x = 0; x < img.width; x += 1) {
* for (let y = 0; y < img.height; y += 1) {
* img.set(x, y, 0);
* }
* }
*
* // Update the image's pixel values.
* img.updatePixels();
*
* // Draw the image.
* image(img, 17, 17);
*
* describe('A black square drawn in the middle of a gray square.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Image object.
* let img = createImage(66, 66);
*
* // Load the image's pixels into memory.
* img.loadPixels();
*
* // Create a color gradient.
* for (let x = 0; x < img.width; x += 1) {
* for (let y = 0; y < img.height; y += 1) {
* // Calculate the transparency.
* let a = map(x, 0, img.width, 0, 255);
*
* // Create a p5.Color object.
* let c = color(0, a);
*
* // Set the pixel's color.
* img.set(x, y, c);
* }
* }
*
* // Update the image's pixels.
* img.updatePixels();
*
* // Display the image.
* image(img, 17, 17);
*
* describe('A square with a horizontal color gradient that transitions from gray to black.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Image object.
* let img = createImage(66, 66);
*
* // Load the pixels into memory.
* img.loadPixels();
* // Get the current pixel density.
* let d = pixelDensity();
*
* // Calculate the pixel that is halfway through the image's pixel array.
* let halfImage = 4 * (d * img.width) * (d * img.height / 2);
*
* // Set half of the image's pixels to black.
* for (let i = 0; i < halfImage; i += 4) {
* // Red.
* img.pixels[i] = 0;
* // Green.
* img.pixels[i + 1] = 0;
* // Blue.
* img.pixels[i + 2] = 0;
* // Alpha.
* img.pixels[i + 3] = 255;
* }
*
* // Update the image's pixels.
* img.updatePixels();
*
* // Display the image.
* image(img, 17, 17);
*
* describe('A black square drawn in the middle of a gray square.');
* }
* </code>
* </div>
*/
fn.createImage = function(width, height) {
// p5._validateParameters('createImage', arguments);
return new p5.Image(width, height);
};
/**
* Saves the current canvas as an image.
*
* By default, `saveCanvas()` saves the canvas as a PNG image called
* `untitled.png`.
*
* The first parameter, `filename`, is optional. It's a string that sets the
* file's name. If a file extension is included, as in
* `saveCanvas('drawing.png')`, then the image will be saved using that
* format.
*
* The second parameter, `extension`, is also optional. It sets the files format.
* Either `'png'`, `'webp'`, or `'jpg'` can be used. For example, `saveCanvas('drawing', 'jpg')`
* saves the canvas to a file called `drawing.jpg`.
*
* Note: The browser will either save the file immediately or prompt the user
* with a dialogue window.
*
* @method saveCanvas
* @param {p5.Framebuffer|p5.Element|HTMLCanvasElement} selectedCanvas reference to a
* specific HTML5 canvas element.
* @param {String} [filename] file name. Defaults to 'untitled'.
* @param {String} [extension] file extension, either 'png', 'webp', or 'jpg'. Defaults to 'png'.
*
* @example
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
* background(255);
*
* // Save the canvas to 'untitled.png'.
* saveCanvas();
*
* describe('A white square.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(255);
*
* // Save the canvas to 'myCanvas.jpg'.
* saveCanvas('myCanvas.jpg');
*
* describe('A white square.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(255);
*
* // Save the canvas to 'myCanvas.jpg'.
* saveCanvas('myCanvas', 'jpg');
*
* describe('A white square.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* let cnv = createCanvas(100, 100);
*
* background(255);
*
* // Save the canvas to 'untitled.png'.
* saveCanvas(cnv);
*
* describe('A white square.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* let cnv = createCanvas(100, 100);
*
* background(255);
*
* // Save the canvas to 'myCanvas.jpg'.
* saveCanvas(cnv, 'myCanvas.jpg');
*
* describe('A white square.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* let cnv = createCanvas(100, 100);
*
* background(255);
*
* // Save the canvas to 'myCanvas.jpg'.
* saveCanvas(cnv, 'myCanvas', 'jpg');
*
* describe('A white square.');
* }
* </code>
* </div>
*/
/**
* @method saveCanvas
* @param {String} [filename]
* @param {String} [extension]
*/
fn.saveCanvas = function(...args) {
// copy arguments to array
let htmlCanvas, filename, extension, temporaryGraphics;
if (args[0] instanceof HTMLCanvasElement) {
htmlCanvas = args[0];
args.shift();
} else if (args[0] instanceof Element) {
htmlCanvas = args[0].elt;
args.shift();
} else if (args[0] instanceof Framebuffer) {
const framebuffer = args[0];
temporaryGraphics = this.createGraphics(framebuffer.width,
framebuffer.height);
temporaryGraphics.pixelDensity(framebuffer.pixelDensity());
framebuffer.loadPixels();
temporaryGraphics.loadPixels();
temporaryGraphics.pixels.set(framebuffer.pixels);
temporaryGraphics.updatePixels();
htmlCanvas = temporaryGraphics._renderer.canvas;
args.shift();
} else {
htmlCanvas = this._curElement && this._curElement.elt;
}
if (args.length >= 1) {
filename = args[0];
}
if (args.length >= 2) {
extension = args[1];
}
extension =
extension ||
fn._checkFileExtension(filename, extension)[1] ||
'png';
let mimeType;
switch (extension) {
default:
//case 'png':
mimeType = 'image/png';
break;
case 'webp':
mimeType = 'image/webp';
break;
case 'jpeg':
case 'jpg':
mimeType = 'image/jpeg';
break;
}
htmlCanvas.toBlob(blob => {
fn.downloadFile(blob, filename, extension);
if(temporaryGraphics) temporaryGraphics.remove();
}, mimeType);
};
// this is the old saveGif, left here for compatibility purposes
// the only place I found it being used was on image/p5.Image.js, on the
// save function. that has been changed to use this function.
fn.encodeAndDownloadGif = function(pImg, filename) {
const props = pImg.gifProperties;
//convert loopLimit back into Netscape Block formatting
let loopLimit = props.loopLimit;
if (loopLimit === 1) {
loopLimit = null;
} else if (loopLimit === null) {
loopLimit = 0;
}
const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames);
const allFramesPixelColors = [];
// Used to determine the occurrence of unique palettes and the frames
// which use them
const paletteFreqsAndFrames = {};
// Pass 1:
//loop over frames and get the frequency of each palette
for (let i = 0; i < props.numFrames; i++) {
const paletteSet = new Set();
const data = props.frames[i].image.data;
const dataLength = data.length;
// The color for each pixel in this frame ( for easier lookup later )
const pixelColors = new Uint32Array(pImg.width * pImg.height);
for (let j = 0, k = 0; j < dataLength; j += 4, k++) {
const r = data[j + 0];
const g = data[j + 1];
const b = data[j + 2];
const color = (r << 16) | (g << 8) | (b << 0);
paletteSet.add(color);
// What color does this pixel have in this frame ?
pixelColors[k] = color;
}
// A way to put use the entire palette as an object key
const paletteStr = [...paletteSet].sort().toString();
if (paletteFreqsAndFrames[paletteStr] === undefined) {
paletteFreqsAndFrames[paletteStr] = { freq: 1, frames: [i] };
} else {
paletteFreqsAndFrames[paletteStr].freq += 1;
paletteFreqsAndFrames[paletteStr].frames.push(i);
}
allFramesPixelColors.push(pixelColors);
}
let framesUsingGlobalPalette = [];
// Now to build the global palette
// Sort all the unique palettes in descending order of their occurrence
const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function(
a,
b
) {
return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq;
});
// The initial global palette is the one with the most occurrence
const globalPalette = palettesSortedByFreq[0]
.split(',')
.map(a => parseInt(a));
framesUsingGlobalPalette = framesUsingGlobalPalette.concat(
paletteFreqsAndFrames[globalPalette].frames
);
const globalPaletteSet = new Set(globalPalette);
// Build a more complete global palette
// Iterate over the remaining palettes in the order of
// their occurrence and see if the colors in this palette which are
// not in the global palette can be added there, while keeping the length
// of the global palette <= 256
for (let i = 1; i < palettesSortedByFreq.length; i++) {
const palette = palettesSortedByFreq[i].split(',').map(a => parseInt(a));
const difference = palette.filter(x => !globalPaletteSet.has(x));
if (globalPalette.length + difference.length <= 256) {
for (let j = 0; j < difference.length; j++) {
globalPalette.push(difference[j]);
globalPaletteSet.add(difference[j]);
}
// All frames using this palette now use the global palette
framesUsingGlobalPalette = framesUsingGlobalPalette.concat(
paletteFreqsAndFrames[palettesSortedByFreq[i]].frames
);
}
}
framesUsingGlobalPalette = new Set(framesUsingGlobalPalette);
// Build a lookup table of the index of each color in the global palette
// Maps a color to its index
const globalIndicesLookup = {};
for (let i = 0; i < globalPalette.length; i++) {
if (!globalIndicesLookup[globalPalette[i]]) {
globalIndicesLookup[globalPalette[i]] = i;
}
}
// force palette to be power of 2
let powof2 = 1;
while (powof2 < globalPalette.length) {
powof2 <<= 1;
}
globalPalette.length = powof2;
// global opts
const opts = {
loop: loopLimit,
palette: new Uint32Array(globalPalette)
};
const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts);
let previousFrame = {};
// Pass 2
// Determine if the frame needs a local palette
// Also apply transparency optimization. This function will often blow up
// the size of a GIF if not for transparency. If a pixel in one frame has
// the same color in the previous frame, that pixel can be marked as
// transparent. We decide one particular color as transparent and make all
// transparent pixels take this color. This helps in later in compression.
for (let i = 0; i < props.numFrames; i++) {
const localPaletteRequired = !framesUsingGlobalPalette.has(i);
const palette = localPaletteRequired ? [] : globalPalette;
const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height);
// Lookup table mapping color to its indices
const colorIndicesLookup = {};
// All the colors that cannot be marked transparent in this frame
const cannotBeTransparent = new Set();
allFramesPixelColors[i].forEach((color, k) => {
if (localPaletteRequired) {
if (colorIndicesLookup[color] === undefined) {
colorIndicesLookup[color] = palette.length;
palette.push(color);
}
pixelPaletteIndex[k] = colorIndicesLookup[color];
} else {
pixelPaletteIndex[k] = globalIndicesLookup[color];
}
if (i > 0) {
// If even one pixel of this color has changed in this frame
// from the previous frame, we cannot mark it as transparent
if (allFramesPixelColors[i - 1][k] !== color) {
cannotBeTransparent.add(color);
}
}
});
const frameOpts = {};
// Transparency optimization
const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a));
if (canBeTransparent.length > 0) {
// Select a color to mark as transparent
const transparent = canBeTransparent[0];
const transparentIndex = localPaletteRequired
? colorIndicesLookup[transparent]
: globalIndicesLookup[transparent];
if (i > 0) {
for (let k = 0; k < allFramesPixelColors[i].length; k++) {
// If this pixel in this frame has the same color in previous frame
if (allFramesPixelColors[i - 1][k] === allFramesPixelColors[i][k]) {
pixelPaletteIndex[k] = transparentIndex;
}
}
frameOpts.transparent = transparentIndex;
// If this frame has any transparency, do not dispose the previous frame
previousFrame.frameOpts.disposal = 1;
}
}
frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting
if (localPaletteRequired) {
// force palette to be power of 2
let powof2 = 1;
while (powof2 < palette.length) {
powof2 <<= 1;
}
palette.length = powof2;
frameOpts.palette = new Uint32Array(palette);
}
if (i > 0) {
// add the frame that came before the current one
gifWriter.addFrame(
0,
0,
pImg.width,
pImg.height,
previousFrame.pixelPaletteIndex,
previousFrame.frameOpts
);
}
// previous frame object should now have details of this frame
previousFrame = {
pixelPaletteIndex,
frameOpts
};
}
previousFrame.frameOpts.disposal = 1;
// add the last frame
gifWriter.addFrame(
0,
0,
pImg.width,
pImg.height,
previousFrame.pixelPaletteIndex,
previousFrame.frameOpts
);
const extension = 'gif';
const blob = new Blob([buffer.slice(0, gifWriter.end())], {
type: 'image/gif'
});
fn.downloadFile(blob, filename, extension);
};
/**
* Captures a sequence of frames from the canvas that can be saved as images.
*
* `saveFrames()` creates an array of frame objects. Each frame is stored as
* an object with its file type, file name, and image data as a string. For
* example, the first saved frame might have the following properties:
*
* `{ ext: 'png', filenmame: 'frame0', imageData: 'data:image/octet-stream;base64, abc123' }`.
*
* The first parameter, `filename`, sets the prefix for the file names. For
* example, setting the prefix to `'frame'` would generate the image files
* `frame0.png`, `frame1.png`, and so on.
*
* The second parameter, `extension`, sets the file type to either `'png'` or
* `'jpg'`.
*
* The third parameter, `duration`, sets the duration to record in seconds.
* The maximum duration is 15 seconds.
*
* The fourth parameter, `framerate`, sets the number of frames to record per
* second. The maximum frame rate value is 22. Limits are placed on `duration`
* and `framerate` to avoid using too much memory. Recording large canvases
* can easily crash sketches or even web browsers.
*
* The fifth parameter, `callback`, is optional. If a function is passed,
* image files won't be saved by default. The callback function can be used
* to process an array containing the data for each captured frame. The array
* of image data contains a sequence of objects with three properties for each
* frame: `imageData`, `filename`, and `extension`.
*
* Note: Frames are downloaded as individual image files by default.
*
* @method saveFrames
* @param {String} filename prefix of file name.
* @param {String} extension file extension, either 'jpg' or 'png'.
* @param {Number} duration duration in seconds to record. This parameter will be constrained to be less or equal to 15.
* @param {Number} framerate number of frames to save per second. This parameter will be constrained to be less or equal to 22.
* @param {function(Array)} [callback] callback function that will be executed
to handle the image data. This function
should accept an array as argument. The
array will contain the specified number of
frames of objects. Each object has three
properties: `imageData`, `filename`, and `extension`.
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* describe('A square repeatedly changes color from blue to pink.');
* }
*
* function draw() {
* let r = frameCount % 255;
* let g = 50;
* let b = 100;
* background(r, g, b);
* }
*
* // Save the frames when the user presses the 's' key.
* function keyPressed() {
* if (key === 's') {
* saveFrames('frame', 'png', 1, 5);
* }
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* describe('A square repeatedly changes color from blue to pink.');
* }
*
* function draw() {
* let r = frameCount % 255;
* let g = 50;
* let b = 100;
* background(r, g, b);
* }
*
* // Print 5 frames when the user presses the mouse.
* function mousePressed() {
* saveFrames('frame', 'png', 1, 5, printFrames);
* }
*
* // Prints an array of objects containing raw image data, filenames, and extensions.
* function printFrames(frames) {
* for (let frame of frames) {
* print(frame);
* }
* }
* </code>
* </div>
*/
fn.saveFrames = function(fName, ext, _duration, _fps, callback) {
// p5._validateParameters('saveFrames', arguments);
let duration = _duration || 3;
duration = Math.max(Math.min(duration, 15), 0);
duration = duration * 1000;
let fps = _fps || 15;
fps = Math.max(Math.min(fps, 22), 0);
let count = 0;
const makeFrame = fn._makeFrame;
const cnv = this._curElement.elt;
let frames = [];
const frameFactory = setInterval(() => {
frames.push(makeFrame(fName + count, ext, cnv));
count++;
}, 1000 / fps);
setTimeout(() => {
clearInterval(frameFactory);
if (callback) {
callback(frames);
} else {
for (const f of frames) {
fn.downloadFile(f.imageData, f.filename, f.ext);
}
}
frames = []; // clear frames
}, duration + 0.01);
};
fn._makeFrame = function(filename, extension, _cnv) {
let cnv;
if (this) {
cnv = this._curElement.elt;
} else {
cnv = _cnv;
}
let mimeType;
if (!extension) {
extension = 'png';
mimeType = 'image/png';
} else {
switch (extension.toLowerCase()) {
case 'png':
mimeType = 'image/png';
break;
case 'jpeg':
mimeType = 'image/jpeg';
break;
case 'jpg':
mimeType = 'image/jpeg';
break;
default:
mimeType = 'image/png';
break;
}
}
const downloadMime = 'image/octet-stream';
let imageData = cnv.toDataURL(mimeType);
imageData = imageData.replace(mimeType, downloadMime);
const thisFrame = {};
thisFrame.imageData = imageData;
thisFrame.filename = filename;
thisFrame.ext = extension;
return thisFrame;
};
}
if(typeof p5 !== 'undefined'){
image(p5, p5.prototype);
}
/**
* @module IO
* @submodule Input
* @for p5
* @requires core
*/
class HTTPError extends Error {
status;
response;
ok;
}
async function request(path, type){
try {
const res = await fetch(path);
if (res.ok) {
let data;
switch(type) {
case 'json':
data = await res.json();
break;
case 'text':
data = await res.text();
break;
case 'arrayBuffer':
data = await res.arrayBuffer();
break;
case 'blob':
data = await res.blob();
break;
case 'bytes':
// TODO: Chrome does not implement res.bytes() yet
if(res.bytes){
data = await res.bytes();
}else {
const d = await res.arrayBuffer();
data = new Uint8Array(d);
}
break;
default:
throw new Error('Unsupported response type');
}
return { data, headers: res.headers };
} else {
const err = new HTTPError(res.statusText);
err.status = res.status;
err.response = res;
err.ok = false;
throw err;
}
} catch(err) {
// Handle both fetch error and HTTP error
if (err instanceof TypeError) {
console.log('You may have encountered a CORS error');
} else if (err instanceof HTTPError) {
console.log('You have encountered a HTTP error');
} else if (err instanceof SyntaxError) {
console.log('There is an error parsing the response to requested data structure');
}
throw err;
}
}
function files(p5, fn){
/**
* Loads a JSON file to create an `Object`.
*
* JavaScript Object Notation
* (<a href="https://developer.mozilla.org/en-US/docs/Glossary/JSON" target="_blank">JSON</a>)
* is a standard format for sending data between applications. The format is
* based on JavaScript objects which have keys and values. JSON files store
* data in an object with strings as keys. Values can be strings, numbers,
* Booleans, arrays, `null`, or other objects.
*
* The first parameter, `path`, is a string with the path to the file.
* Paths to local files should be relative, as in
* `loadJSON('assets/data.json')`. URLs such as
* `'https://example.com/data.json'` may be blocked due to browser security.
* The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* object for more advanced usage.
*
* The second parameter, `successCallback`, is optional. If a function is
* passed, as in `loadJSON('assets/data.json', handleData)`, then the
* `handleData()` function will be called once the data loads. The object
* created from the JSON data will be passed to `handleData()` as its only argument.
* The return value of the `handleData()` function will be used as the final return
* value of `loadJSON('assets/data.json', handleData)`.
*
* The third parameter, `failureCallback`, is also optional. If a function is
* passed, as in `loadJSON('assets/data.json', handleData, handleFailure)`,
* then the `handleFailure()` function will be called if an error occurs while
* loading. The `Error` object will be passed to `handleFailure()` as its only
* argument. The return value of the `handleFailure()` function will be used as the
* final return value of `loadJSON('assets/data.json', handleData, handleFailure)`.
*
* This function returns a `Promise` and should be used in an `async` setup with
* `await`. See the examples for the usage syntax.
*
* @method loadJSON
* @param {String|Request} path path of the JSON file to be loaded.
* @param {Function} [successCallback] function to call once the data is loaded. Will be passed the object.
* @param {Function} [errorCallback] function to call if the data fails to load. Will be passed an `Error` event object.
* @return {Promise<Object>} object containing the loaded data.
*
* @example
*
* <div>
* <code>
* let myData;
*
* async function setup() {
* myData = await loadJSON('assets/data.json');
* createCanvas(100, 100);
*
* background(200);
*
* // Style the circle.
* fill(myData.color);
* noStroke();
*
* // Draw the circle.
* circle(myData.x, myData.y, myData.d);
*
* describe('A pink circle on a gray background.');
* }
* </code>
* </div>
*
* <div>
* <code>
* let myData;
*
* async function setup() {
* myData = await loadJSON('assets/data.json');
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object and make it transparent.
* let c = color(myData.color);
* c.setAlpha(80);
*
* // Style the circles.
* fill(c);
* noStroke();
*
* // Iterate over the myData.bubbles array.
* for (let b of myData.bubbles) {
* // Draw a circle for each bubble.
* circle(b.x, b.y, b.d);
* }
*
* describe('Several pink bubbles floating in a blue sky.');
* }
* </code>
* </div>
*
* <div>
* <code>
* let myData;
*
* async function setup() {
* myData = await loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
* createCanvas(100, 100);
*
* background(200);
*
* // Get data about the most recent earthquake.
* let quake = myData.features[0].properties;
*
* // Draw a circle based on the earthquake's magnitude.
* circle(50, 50, quake.mag * 10);
*
* // Style the text.
* textAlign(LEFT, CENTER);
* textFont('Courier New');
* textSize(11);
*
* // Display the earthquake's location.
* text(quake.place, 5, 80, 100);
*
* describe(`A white circle on a gray background. The text "${quake.place}" is written beneath the circle.`);
* }
* </code>
* </div>
*
* <div>
* <code>
* let bigQuake;
*
* // Load the GeoJSON and preprocess it.
* async function setup() {
* await loadJSON(
* 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
* handleData
* );
*
* createCanvas(100, 100);
*
* background(200);
*
* // Draw a circle based on the earthquake's magnitude.
* circle(50, 50, bigQuake.mag * 10);
*
* // Style the text.
* textAlign(LEFT, CENTER);
* textFont('Courier New');
* textSize(11);
*
* // Display the earthquake's location.
* text(bigQuake.place, 5, 80, 100);
*
* describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`);
* }
*
* // Find the biggest recent earthquake.
* function handleData(data) {
* let maxMag = 0;
* // Iterate over the earthquakes array.
* for (let quake of data.features) {
* // Reassign bigQuake if a larger
* // magnitude quake is found.
* if (quake.properties.mag > maxMag) {
* bigQuake = quake.properties;
* }
* }
* }
* </code>
* </div>
*
* <div>
* <code>
* let bigQuake;
*
* // Load the GeoJSON and preprocess it.
* async function setup() {
* await loadJSON(
* 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
* handleData,
* handleError
* );
*
* createCanvas(100, 100);
*
* background(200);
*
* // Draw a circle based on the earthquake's magnitude.
* circle(50, 50, bigQuake.mag * 10);
*
* // Style the text.
* textAlign(LEFT, CENTER);
* textFont('Courier New');
* textSize(11);
*
* // Display the earthquake's location.
* text(bigQuake.place, 5, 80, 100);
*
* describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`);
* }
*
* // Find the biggest recent earthquake.
* function handleData(data) {
* let maxMag = 0;
* // Iterate over the earthquakes array.
* for (let quake of data.features) {
* // Reassign bigQuake if a larger
* // magnitude quake is found.
* if (quake.properties.mag > maxMag) {
* bigQuake = quake.properties;
* }
* }
* }
*
* // Log any errors to the console.
* function handleError(error) {
* console.log('Oops!', error);
* }
* </code>
* </div>
*/
fn.loadJSON = async function (path, successCallback, errorCallback) {
// p5._validateParameters('loadJSON', arguments);
try{
const { data } = await request(path, 'json');
if (successCallback) return successCallback(data);
return data;
} catch(err) {
p5._friendlyFileLoadError(5, path);
if(errorCallback) {
return errorCallback(err);
} else {
throw err;
}
}
};
/**
* Loads a text file to create an `Array`.
*
* The first parameter, `path`, is always a string with the path to the file.
* Paths to local files should be relative, as in
* `loadStrings('assets/data.txt')`. URLs such as
* `'https://example.com/data.txt'` may be blocked due to browser security.
* The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* object for more advanced usage.
*
* The second parameter, `successCallback`, is optional. If a function is
* passed, as in `loadStrings('assets/data.txt', handleData)`, then the
* `handleData()` function will be called once the data loads. The array
* created from the text data will be passed to `handleData()` as its only
* argument. The return value of the `handleData()` function will be used as
* the final return value of `loadStrings('assets/data.txt', handleData)`.
*
* The third parameter, `failureCallback`, is also optional. If a function is
* passed, as in `loadStrings('assets/data.txt', handleData, handleFailure)`,
* then the `handleFailure()` function will be called if an error occurs while
* loading. The `Error` object will be passed to `handleFailure()` as its only
* argument. The return value of the `handleFailure()` function will be used as
* the final return value of `loadStrings('assets/data.txt', handleData, handleFailure)`.
*
* This function returns a `Promise` and should be used in an `async` setup with
* `await`. See the examples for the usage syntax.
*
* @method loadStrings
* @param {String|Request} path path of the text file to be loaded.
* @param {Function} [successCallback] function to call once the data is
* loaded. Will be passed the array.
* @param {Function} [errorCallback] function to call if the data fails to
* load. Will be passed an `Error` event
* object.
* @return {Promise<String[]>} new array containing the loaded text.
*
* @example
*
* <div>
* <code>
* let myData;
*
* async function setup() {
* myData = await loadStrings('assets/test.txt');
*
* createCanvas(100, 100);
*
* background(200);
*
* // Select a random line from the text.
* let phrase = random(myData);
*
* // Style the text.
* textAlign(LEFT, CENTER);
* textFont('Courier New');
* textSize(12);
*
* // Display the text.
* text(phrase, 10, 50, 90);
*
* describe(`The text "${phrase}" written in black on a gray background.`);
* }
* </code>
* </div>
*
* <div>
* <code>
* let lastLine;
*
* // Load the text and preprocess it.
* async function setup() {
* await loadStrings('assets/test.txt', handleData);
*
* createCanvas(100, 100);
*
* background(200);
*
* // Style the text.
* textAlign(LEFT, CENTER);
* textFont('Courier New');
* textSize(12);
*
* // Display the text.
* text(lastLine, 10, 50, 90);
*
* describe('The text "I talk like an orange" written in black on a gray background.');
* }
*
* // Select the last line from the text.
* function handleData(data) {
* lastLine = data[data.length - 1];
* }
* </code>
* </div>
*
* <div>
* <code>
* let lastLine;
*
* // Load the text and preprocess it.
* async function setup() {
* await loadStrings('assets/test.txt', handleData, handleError);
*
* createCanvas(100, 100);
*
* background(200);
*
* // Style the text.
* textAlign(LEFT, CENTER);
* textFont('Courier New');
* textSize(12);
*
* // Display the text.
* text(lastLine, 10, 50, 90);
*
* describe('The text "I talk like an orange" written in black on a gray background.');
* }
*
* // Select the last line from the text.
* function handleData(data) {
* lastLine = data[data.length - 1];
* }
*
* // Log any errors to the console.
* function handleError(error) {
* console.error('Oops!', error);
* }
* </code>
* </div>
*/
fn.loadStrings = async function (path, successCallback, errorCallback) {
// p5._validateParameters('loadStrings', arguments);
try{
let { data } = await request(path, 'text');
data = data.split(/\r?\n/);
if (successCallback) return successCallback(data);
return data;
} catch(err) {
p5._friendlyFileLoadError(3, path);
if(errorCallback) {
return errorCallback(err);
} else {
throw err;
}
}
};
/**
* Reads the contents of a file or URL and creates a <a href="#/p5.Table">p5.Table</a> object with
* its values. If a file is specified, it must be located in the sketch's
* "data" folder. The filename parameter can also be a URL to a file found
* online. By default, the file is assumed to be comma-separated (in CSV
* format). Table only looks for a header row if the 'header' option is
* included.
*
* This function returns a `Promise` and should be used in an `async` setup with
* `await`. See the examples for the usage syntax.
*
* All files loaded and saved use UTF-8 encoding. This method is suitable for fetching files up to size of 64MB.
*
* @method loadTable
* @deprecated p5.Table will be removed in a future version of p5.js to make way for a new, friendlier version :)
* @param {String|Request} filename name of the file or URL to load
* @param {String} [separator] the separator character used by the file, defaults to `','`
* @param {String} [header] "header" to indicate table has header row
* @param {Function} [callback] function to be executed after
* <a href="#/p5/loadTable">loadTable()</a> completes. On success, the
* <a href="#/p5.Table">Table</a> object is passed in as the
* first argument.
* @param {Function} [errorCallback] function to be executed if
* there is an error, response is passed
* in as first argument
* @return {Promise<Object>} <a href="#/p5.Table">Table</a> object containing data
*
* @example
* <div class='norender'>
* <code>
* let table;
*
* async function setup() {
* // Create a 200x200 canvas
* createCanvas(200, 200);
*
* // Load the CSV file with a header row
* table = await loadTable('assets/mammals.csv', ',', 'header');
*
* // Get the second row (index 1)
* let row = table.getRow(1);
*
* // Set text properties
* fill(0); // Set text color to black
* textSize(16); // Adjust text size as needed
*
* // Display each column value in the row on the canvas.
* // Using an offset for y-position so each value appears on a new line.
* for (let c = 0; c < table.getColumnCount(); c++) {
* text(row.getString(c), 10, 30 + c * 20);
* }
* }
* </code>
* </div>
*/
fn.loadTable = async function (path, separator, header, successCallback, errorCallback) {
if(typeof arguments[arguments.length-1] === 'function'){
if(typeof arguments[arguments.length-2] === 'function'){
successCallback = arguments[arguments.length-2];
errorCallback = arguments[arguments.length-1];
}else {
successCallback = arguments[arguments.length-1];
}
}
if(typeof separator !== 'string') separator = ',';
if(typeof header === 'function') header = false;
try{
let { data } = await request(path, 'text');
let ret = new p5.Table();
data = parse(data, {
separator
});
if(header){
ret.columns = data.shift();
}else {
ret.columns = Array(data[0].length).fill(null);
}
data.forEach((line) => {
const row = new p5.TableRow(line);
ret.addRow(row);
});
if (successCallback) {
return successCallback(ret);
} else {
return ret;
}
} catch(err) {
p5._friendlyFileLoadError(2, path);
if(errorCallback) {
return errorCallback(err);
} else {
throw err;
}
}
};
/**
* Loads an XML file to create a <a href="#/p5.XML">p5.XML</a> object.
*
* Extensible Markup Language
* (<a href="https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction" target="_blank">XML</a>)
* is a standard format for sending data between applications. Like HTML, the
* XML format is based on tags and attributes, as in
* `<time units="s">1234</time>`.
*
* The first parameter, `path`, is always a string with the path to the file.
* Paths to local files should be relative, as in
* `loadXML('assets/data.xml')`. URLs such as `'https://example.com/data.xml'`
* may be blocked due to browser security. The `path` parameter can also be defined
* as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* object for more advanced usage.
*
* The second parameter, `successCallback`, is optional. If a function is
* passed, as in `loadXML('assets/data.xml', handleData)`, then the
* `handleData()` function will be called once the data loads. The
* <a href="#/p5.XML">p5.XML</a> object created from the data will be passed
* to `handleData()` as its only argument. The return value of the `handleData()`
* function will be used as the final return value of `loadXML('assets/data.xml', handleData)`.
*
* The third parameter, `failureCallback`, is also optional. If a function is
* passed, as in `loadXML('assets/data.xml', handleData, handleFailure)`, then
* the `handleFailure()` function will be called if an error occurs while
* loading. The `Error` object will be passed to `handleFailure()` as its only
* argument. The return value of the `handleFailure()` function will be used as the
* final return value of `loadXML('assets/data.xml', handleData, handleFailure)`.
*
* This function returns a `Promise` and should be used in an `async` setup with
* `await`. See the examples for the usage syntax.
*
* @method loadXML
* @param {String|Request} path path of the XML file to be loaded.
* @param {Function} [successCallback] function to call once the data is
* loaded. Will be passed the
* <a href="#/p5.XML">p5.XML</a> object.
* @param {Function} [errorCallback] function to call if the data fails to
* load. Will be passed an `Error` event
* object.
* @return {Promise<p5.XML>} XML data loaded into a <a href="#/p5.XML">p5.XML</a>
* object.
*
* @example
* <div>
* <code>
* let myXML;
*
* // Load the XML and create a p5.XML object.
* async function setup() {
* myXML = await loadXML('assets/animals.xml');
*
* createCanvas(100, 100);
*
* background(200);
*
* // Get an array with all mammal tags.
* let mammals = myXML.getChildren('mammal');
*
* // Style the text.
* textAlign(LEFT, CENTER);
* textFont('Courier New');
* textSize(14);
*
* // Iterate over the mammals array.
* for (let i = 0; i < mammals.length; i += 1) {
*
* // Calculate the y-coordinate.
* let y = (i + 1) * 25;
*
* // Get the mammal's common name.
* let name = mammals[i].getContent();
*
* // Display the mammal's name.
* text(name, 20, y);
* }
*
* describe(
* 'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.'
* );
* }
* </code>
* </div>
*
* <div>
* <code>
* let lastMammal;
*
* // Load the XML and create a p5.XML object.
* async function setup() {
* await loadXML('assets/animals.xml', handleData);
*
* createCanvas(100, 100);
*
* background(200);
*
* // Style the text.
* textAlign(CENTER, CENTER);
* textFont('Courier New');
* textSize(16);
*
* // Display the content of the last mammal element.
* text(lastMammal, 50, 50);
*
* describe('The word "Zebra" written in black on a gray background.');
* }
*
* // Get the content of the last mammal element.
* function handleData(data) {
* // Get an array with all mammal elements.
* let mammals = data.getChildren('mammal');
*
* // Get the content of the last mammal.
* lastMammal = mammals[mammals.length - 1].getContent();
* }
* </code>
* </div>
*
* <div>
* <code>
* let lastMammal;
*
* // Load the XML and preprocess it.
* async function setup() {
* await loadXML('assets/animals.xml', handleData, handleError);
*
* createCanvas(100, 100);
*
* background(200);
*
* // Style the text.
* textAlign(CENTER, CENTER);
* textFont('Courier New');
* textSize(16);
*
* // Display the content of the last mammal element.
* text(lastMammal, 50, 50);
*
* describe('The word "Zebra" written in black on a gray background.');
* }
*
* // Get the content of the last mammal element.
* function handleData(data) {
* // Get an array with all mammal elements.
* let mammals = data.getChildren('mammal');
*
* // Get the content of the last mammal.
* lastMammal = mammals[mammals.length - 1].getContent();
* }
*
* // Log any errors to the console.
* function handleError(error) {
* console.error('Oops!', error);
* }
* </code>
* </div>
*/
fn.loadXML = async function (path, successCallback, errorCallback) {
try{
const parser = new DOMParser();
let { data } = await request(path, 'text');
const parsedDOM = parser.parseFromString(data, 'application/xml');
data = new p5.XML(parsedDOM);
if (successCallback) return successCallback(data);
return data;
} catch(err) {
p5._friendlyFileLoadError(1, path);
if(errorCallback) {
return errorCallback(err);
} else {
throw err;
}
}
};
/**
* This method is suitable for fetching files up to size of 64MB.
*
* @method loadBytes
* @param {String|Request} file name of the file or URL to load
* @param {Function} [callback] function to be executed after <a href="#/p5/loadBytes">loadBytes()</a>
* completes
* @param {Function} [errorCallback] function to be executed if there
* is an error
* @returns {Promise<Uint8Array>} a Uint8Array containing the loaded buffer
*
* @example
*
* <div>
* <code>
* let data;
*
* async function setup() {
* createCanvas(100, 100); // Create a canvas
* data = await loadBytes('assets/mammals.xml'); // Load the bytes from the XML file
*
* background(255); // Set a white background
* fill(0); // Set text color to black
*
* // Display the first 5 byte values on the canvas in hexadecimal format
* for (let i = 0; i < 5; i++) {
* let byteHex = data[i].toString(16);
* text(byteHex, 10, 18 * (i + 1)); // Adjust spacing as needed
* }
*
* describe('no image displayed, displays first 5 bytes of mammals.xml in hexadecimal format');
* }
* </code>
* </div>
*/
fn.loadBytes = async function (path, successCallback, errorCallback) {
try{
let { data } = await request(path, 'arrayBuffer');
data = new Uint8Array(data);
if (successCallback) return successCallback(data);
return data;
} catch(err) {
p5._friendlyFileLoadError(6, path);
if(errorCallback) {
return errorCallback(err);
} else {
throw err;
}
}
};
/**
* Loads a file at the given path as a Blob, then returns the resulting data or
* passes it to a success callback function, if provided. On load, this function
* returns a `Promise` that resolves to a Blob containing the file data.
*
* @method loadBlob
* @param {String|Request} path - The path or Request object pointing to the file
* you want to load.
* @param {Function}