*
* The numbers can be passed individually, as in
* `applyMatrix(2, 0, 0, 0, 2, 0)`. They can also be passed in an array, as in
* `applyMatrix([2, 0, 0, 0, 2, 0])`.
*
* In 3D mode, the parameters `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`,
* `j`, `k`, `l`, `m`, `n`, `o`, and `p` correspond to elements in the
* following transformation matrix:
*
*
*
* The numbers can be passed individually, as in
* `applyMatrix(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1)`. They can
* also be passed in an array, as in
* `applyMatrix([2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1])`.
*
* By default, transformations accumulate. The
* push() and pop() functions
* can be used to isolate transformations within distinct drawing groups.
*
* Note: Transformations are reset at the beginning of the draw loop. Calling
* `applyMatrix()` inside the draw() function won't
* cause shapes to transform continuously.
*
* @method applyMatrix
* @param {Array
*
*
* Passing in `CLOSE` to `endShape()` closes the spline smoothly.
* ```js
* beginShape();
*
* // Add the first point.
* splineVertex(25, 80);
*
* // Add the second point.
* splineVertex(20, 30);
*
* // Add the second point.
* splineVertex(85, 60);
*
* endShape(CLOSE);
* ```
*
*
*
*
* By default (`ends: INCLUDE`), the curve passes through
* all the points you add with `splineVertex()`, similar to
* the spline() function. To draw only
* the middle span p1->p2 (skipping p0->p1 and p2->p3), set
* `splineProperty('ends', EXCLUDE)`. You don’t need to duplicate
* vertices to draw those spans.
*
* Spline curves can also be drawn in 3D using WebGL mode. The 3D version of
* `splineVertex()` has three arguments because each point has x-, y-, and
* z-coordinates. By default, the vertex’s z-coordinate is set to 0.
*
* Note: `splineVertex()` won’t work when an argument is passed to
* beginShape().
*
* @method splineVertex
* @param {Number} x x-coordinate of the vertex
* @param {Number} y y-coordinate of the vertex
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(220);
* noFill();
* strokeWeight(1);
*
* beginShape();
* splineVertex(25, 80);
* splineVertex(20, 30);
* splineVertex(85, 60);
* endShape();
*
* strokeWeight(5);
* stroke(0);
*
* point(25, 80);
* point(20, 30);
* point(85, 60);
*
* describe(
* 'On a gray background, a black spline passes through three marked points.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
* background(220);
*
* beginShape();
* splineVertex(25, 80);
* splineVertex(20, 30);
* splineVertex(85, 60);
* endShape(CLOSE);
*
* describe(
* 'On a gray background, a closed black spline with a white interior forms a triangular shape with smooth corners.'
* );
* }
*
* @example
* let ringInnerRadius, ringWidth;
* let radius, dRadius;
* let theta, dTheta;
* let time, dTime;
* let vertexCount, unit, offset;
*
* function setup() {
* createCanvas(400, 400);
*
* vertexCount = 15;
* unit = createVector(1, 0);
* dTheta = TAU / vertexCount;
* dTime = 0.004;
*
* ringInnerRadius = 25;
* ringWidth = 5 * ringInnerRadius;
*
* offset = width;
*
* describe(
* 'A white blob with a black outline changes its shape over time.'
* );
* }
*
* function draw() {
* background(220);
* strokeWeight(2);
* translate(width / 2, height / 2);
*
* time = dTime * frameCount;
*
* beginShape();
* for (let i = 0; i < vertexCount; i++) {
* unit.rotate(dTheta);
* dRadius = noise(offset + unit.x, offset + unit.y, time) * ringWidth;
* radius = ringInnerRadius + dRadius;
* splineVertex(radius * unit.x, radius * unit.y);
* }
* endShape(CLOSE);
* }
*
* @example
* let vertexA;
* let vertexB;
* let vertexC;
* let vertexD;
* let vertexE;
* let vertexF;
*
* let markerRadius;
*
* let vectorAB;
* let vectorFE;
*
* let endOfTangentB;
* let endOfTangentE;
*
* function setup() {
* createCanvas(100, 100);
*
* // Initialize variables
* // Adjusting vertices A and F affects the slopes at B and E
*
* vertexA = createVector(35, 85);
* vertexB = createVector(25, 70);
* vertexC = createVector(30, 30);
* vertexD = createVector(70, 30);
* vertexE = createVector(75, 70);
* vertexF = createVector(65, 85);
*
* markerRadius = 4;
*
* vectorAB = p5.Vector.sub(vertexB, vertexA);
* vectorFE = p5.Vector.sub(vertexE, vertexF);
*
* endOfTangentB = p5.Vector.add(vertexC, vectorAB);
* endOfTangentE = p5.Vector.add(vertexD, vectorFE);
*
* splineProperty(`ends`, EXCLUDE);
*
* // Draw figure
*
* background(220);
*
* noFill();
*
* beginShape();
* splineVertex(vertexA.x, vertexA.y);
* splineVertex(vertexB.x, vertexB.y);
* splineVertex(vertexC.x, vertexC.y);
* splineVertex(vertexD.x, vertexD.y);
* splineVertex(vertexE.x, vertexE.y);
* splineVertex(vertexF.x, vertexF.y);
* endShape();
*
* stroke('red');
* line(vertexA.x, vertexA.y, vertexC.x, vertexC.y);
* line(vertexB.x, vertexB.y, endOfTangentB.x, endOfTangentB.y);
*
* stroke('blue');
* line(vertexD.x, vertexD.y, vertexF.x, vertexF.y);
* line(vertexE.x, vertexE.y, endOfTangentE.x, endOfTangentE.y);
*
* fill('white');
* stroke('black');
* circle(vertexA.x, vertexA.y, markerRadius);
* circle(vertexB.x, vertexB.y, markerRadius);
* circle(vertexC.x, vertexC.y, markerRadius);
* circle(vertexD.x, vertexD.y, markerRadius);
* circle(vertexE.x, vertexE.y, markerRadius);
* circle(vertexF.x, vertexF.y, markerRadius);
*
* fill('black');
* noStroke();
* text('A', vertexA.x - 15, vertexA.y + 5);
* text('B', vertexB.x - 15, vertexB.y + 5);
* text('C', vertexC.x - 5, vertexC.y - 5);
* text('D', vertexD.x - 5, vertexD.y - 5);
* text('E', vertexE.x + 5, vertexE.y + 5);
* text('F', vertexF.x + 5, vertexF.y + 5);
*
* describe('On a gray background, a black spline passes through vertices A, B, C, D, E, and F, shown as white circles. A red line segment joining vertices A and C has the same slope as the red tangent segment at B. Similarly, the blue line segment joining vertices D and F has the same slope as the blue tangent at E.');
* }
*/
/**
* @method splineVertex
* @param {Number} x
* @param {Number} y
* @param {Number} [z] z-coordinate of the vertex.
* @chainable
*
* @example
* // Click and drag the mouse to view the scene from different angles.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A ghost shape drawn in white on a blue background. When the user drags the mouse, the scene rotates to reveal the outline of a second ghost.');
* }
*
* function draw() {
* background('midnightblue');
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Draw the first ghost.
* noStroke();
* fill('ghostwhite');
*
* beginShape();
* splineVertex(-28, 41, 0);
* splineVertex(-28, 41, 0);
* splineVertex(-29, -33, 0);
* splineVertex(18, -31, 0);
* splineVertex(34, 41, 0);
* splineVertex(34, 41, 0);
* endShape();
*
* // Draw the second ghost.
* noFill();
* stroke('ghostwhite');
*
* beginShape();
* splineVertex(-28, 41, -20);
* splineVertex(-28, 41, -20);
* splineVertex(-29, -33, -20);
* splineVertex(18, -31, -20);
* splineVertex(34, 41, -20);
* splineVertex(34, 41, -20);
* endShape();
* }
*/
/**
* @method splineVertex
* @param {Number} x
* @param {Number} y
* @param {Number} [u=0]
* @param {Number} [v=0]
*/
/**
* @method splineVertex
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @param {Number} [u=0]
* @param {Number} [v=0]
*/
fn.splineVertex = function(...args) {
let x = 0, y = 0, z = 0, u = 0, v = 0;
if (args.length === 2) {
[x, y] = args;
} else if (args.length === 4) {
[x, y, u, v] = args;
} else if (args.length === 3) {
[x, y, z] = args;
} else if (args.length === 5) {
[x, y, z, u, v] = args;
}
this._renderer.splineVertex(x, y, z, u, v);
};
/**
* Gets or sets a given spline property.
*
* Use `splineProperty()` to adjust the behavior of splines
* created with `splineVertex()` or `spline()`. You can control
* two key aspects of a spline: its end behavior (`ends`) and
* its curvature (`tightness`).
*
* By default, the ends property is set to `INCLUDE`, which means
* the spline passes through every point, including the endpoints.
* You can also set it to `EXCLUDE` i.e. `splineProperty('ends', EXCLUDE)`,
* which makes the spline pass through all points except the endpoints.
*
* `INCLUDE` case will have the spline passing through
* all points, like this:
*
* ```js
* splineProperty('ends', INCLUDE); // no need to set this, as it is the default
* spline(25, 46, 93, 44, 93, 81, 35, 85);
*
* point(25, 46);
* point(93, 44);
* point(93, 81);
* point(35, 85);
* ```
*
*
*
*
* EXCLUDE case will have the spline passing through
* the middle points, like this:
*
*
* ```js
* splineProperty('ends', EXCLUDE);
* spline(25, 46, 93, 44, 93, 81, 35, 85);
*
* point(25, 46);
* point(93, 44);
* point(93, 81);
* point(35, 85);
* ```
*
*
*
* By default, the tightness property is set to `0`,
* producing a smooth curve that passes evenly through
* the vertices. Negative values make the curve looser,
* while positive values make it tighter. Common values
* range between -1 and 1, though values outside this
* range can also be used for different effects.
*
* For example, To set tightness, use `splineProperty('tightness', t)`,
* (default: t = 0).
*
* Here's the example showing negetive value of tightness,
* which creates a rounder bulge:
*
* ```js
* splineProperty('tightness', -5)
* stroke(0);
* strokeWeight(2);
* spline(25, 46, 93, 44, 93, 81, 35, 85);
* ```
*
* Here's the example showing positive value of tightness,
* which makes the curve tighter and more angular:
*
* ```js
* splineProperty('tightness', 5)
* stroke(0);
* strokeWeight(2);
* spline(25, 46, 93, 44, 93, 81, 35, 85);
* ```
*
*
* In all cases, the splines in p5.js are cardinal splines.
* When tightness is 0, these splines are often known as
* Catmull-Rom splines
*
* @method splineProperty
* @param {String} property
* @param value Value to set the given property to.
*
* @example
* // Move the mouse left and right to see the curve change.
*
* let t;
*
* function setup() {
* createCanvas(100, 100);
* }
*
* function draw() {
* background(240);
*
* t = map(mouseX, 0, width, -5, 5, true);
* splineProperty('tightness', t);
*
* noFill();
* stroke(0);
* strokeWeight(2);
*
* beginShape();
* splineVertex(10, 26);
* splineVertex(83, 24);
*
* splineVertex(83, 61);
* splineVertex(25, 65);
* endShape();
*
* push();
* strokeWeight(5);
* point(10, 26);
* point(83, 24);
* point(83, 61);
* point(25, 65);
* pop();
*
* fill(0);
* noStroke();
* textSize(10);
* text(`tightness: ${round(t, 1)}`, 15, 90);
* describe('A black spline forms a sideways U shape through four points. The spline passes through the points more loosely as the mouse moves left of center (negative tightness), and more tightly as it moves right of center (positive tightness). The tightness is displayed at the bottom.');
* }
*
* @example
* function setup() {
* createCanvas(360, 140);
* background(240);
* noFill();
*
* // Right panel: ends = INCLUDE (all spans).
* push();
* translate(10, 10);
* stroke(220);
* rect(0, 0, 160, 120);
* fill(30);
* textSize(11);
* text('ends: INCLUDE (all spans)', 8, 16);
* noFill();
*
* splineProperty('ends', INCLUDE);
* stroke(0);
* strokeWeight(2);
* spline(25, 46, 93, 44, 93, 81, 35, 85);
*
* // vertices
* strokeWeight(5);
* stroke(0);
* point(25, 46);
* point(93, 44);
* point(93, 81);
* point(35, 85);
* pop();
*
* // Right panel: ends = EXCLUDE (middle only).
* push();
* translate(190, 10);
* stroke(220);
* rect(0, 0, 160, 120);
* noStroke();
* fill(30);
* text('ends: EXCLUDE ', 18, 16);
* noFill();
*
* splineProperty('ends', EXCLUDE);
* stroke(0);
* strokeWeight(2);
* spline(25, 46, 93, 44, 93, 81, 35, 85);
*
* // vertices
* strokeWeight(5);
* stroke(0);
* point(25, 46);
* point(93, 44);
* point(93, 81);
* point(35, 85);
* pop();
*
* describe('Left panel shows spline with ends INCLUDE (three spans). Right panel shows EXCLUDE (only the middle span). Four black points mark the vertices.');
* }
*
* @example
* let vertexA;
* let vertexB;
* let vertexC;
* let vertexD;
* let vertexE;
* let vertexF;
*
* let markerRadius;
*
* let vectorAB;
* let vectorFE;
*
* let endOfTangentB;
* let endOfTangentE;
*
* function setup() {
* createCanvas(100, 100);
*
* // Initialize variables
* // Adjusting vertices A and F affects the slopes at B and E
*
* vertexA = createVector(35, 85);
* vertexB = createVector(25, 70);
* vertexC = createVector(30, 30);
* vertexD = createVector(70, 30);
* vertexE = createVector(75, 70);
* vertexF = createVector(65, 85);
*
* markerRadius = 4;
*
* vectorAB = p5.Vector.sub(vertexB, vertexA);
* vectorFE = p5.Vector.sub(vertexE, vertexF);
*
* endOfTangentB = p5.Vector.add(vertexC, vectorAB);
* endOfTangentE = p5.Vector.add(vertexD, vectorFE);
*
* splineProperty(`ends`, EXCLUDE);
*
* // Draw figure
*
* background(220);
*
* noFill();
*
* beginShape();
* splineVertex(vertexA.x, vertexA.y);
* splineVertex(vertexB.x, vertexB.y);
* splineVertex(vertexC.x, vertexC.y);
* splineVertex(vertexD.x, vertexD.y);
* splineVertex(vertexE.x, vertexE.y);
* splineVertex(vertexF.x, vertexF.y);
* endShape();
*
* stroke('red');
* line(vertexA.x, vertexA.y, vertexC.x, vertexC.y);
* line(vertexB.x, vertexB.y, endOfTangentB.x, endOfTangentB.y);
*
* stroke('blue');
* line(vertexD.x, vertexD.y, vertexF.x, vertexF.y);
* line(vertexE.x, vertexE.y, endOfTangentE.x, endOfTangentE.y);
*
* fill('white');
* stroke('black');
* circle(vertexA.x, vertexA.y, markerRadius);
* circle(vertexB.x, vertexB.y, markerRadius);
* circle(vertexC.x, vertexC.y, markerRadius);
* circle(vertexD.x, vertexD.y, markerRadius);
* circle(vertexE.x, vertexE.y, markerRadius);
* circle(vertexF.x, vertexF.y, markerRadius);
*
* fill('black');
* noStroke();
* text('A', vertexA.x - 15, vertexA.y + 5);
* text('B', vertexB.x - 15, vertexB.y + 5);
* text('C', vertexC.x - 5, vertexC.y - 5);
* text('D', vertexD.x - 5, vertexD.y - 5);
* text('E', vertexE.x + 5, vertexE.y + 5);
* text('F', vertexF.x + 5, vertexF.y + 5);
*
* describe('On a gray background, a black spline passes through vertices A, B, C, D, E, and F, shown as white circles. A red line segment joining vertices A and C has the same slope as the red tangent segment at B. Similarly, the blue line segment joining vertices D and F has the same slope as the blue tangent at E.');
* }
*/
/**
* @method splineProperty
* @param {String} property
* @returns The current value for the given property.
*/
fn.splineProperty = function(property, value) {
return this._renderer.splineProperty(property, value);
};
/**
* Sets multiple properties for spline curves at once.
*
* `splineProperties()` accepts an object with key-value pairs to configure
* how spline curves are drawn. This is a convenient way to set multiple
* spline properties with a single function call, rather than calling
* splineProperty() multiple times.
*
* The properties object can include:
* - `tightness`: A number that controls how tightly the curve fits to the
* vertex points. The default value is 0. Positive values make the curve
* tighter (straighter), while negative values make it looser. Values
* between -5 and 5 work best.
* - `ends`: Controls whether to draw the end segments of the spline. Set to
* `EXCLUDE` to skip drawing the segments between the first and second
* points and between the second-to-last and last points. This is useful
* when you want to use the first and last points as control points only.
*
* `splineProperties()` affects curves drawn with
* splineVertex() within
* beginShape() and
* endShape(), as well as curves drawn with
* spline(). The properties remain active until
* changed by another call to `splineProperties()` or
* splineProperty().
*
* @method splineProperties
* @param {Object} values an object containing spline property key-value pairs
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
* background(220);
*
* // Set spline tightness using splineProperties
* splineProperties({
* tightness: 0.5
* });
*
* // Draw a spline curve
* noFill();
* stroke(0);
* strokeWeight(2);
*
* beginShape();
* splineVertex(20, 80);
* splineVertex(30, 30);
* splineVertex(70, 30);
* splineVertex(80, 80);
* endShape();
*
* // Show vertex points
* fill(255, 0, 0);
* noStroke();
* circle(20, 80, 6);
* circle(30, 30, 6);
* circle(70, 30, 6);
* circle(80, 80, 6);
*
* describe('A smooth curved line with tightness 0.5 connecting four red points.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
* background(220);
*
* // Exclude end segments - first and last points become control points
* splineProperties({
* tightness: 0,
* ends: EXCLUDE
* });
*
* // Draw curve only between middle points
* noFill();
* stroke(0);
* strokeWeight(2);
*
* beginShape();
* splineVertex(10, 50); // Control point (affects curve but not drawn to)
* splineVertex(30, 20); // Start of visible curve
* splineVertex(70, 80); // End of visible curve
* splineVertex(90, 50); // Control point (affects curve but not drawn to)
* endShape();
*
* // Show all points
* fill(200, 0, 0);
* noStroke();
* circle(10, 50, 6); // Control point
* circle(90, 50, 6); // Control point
*
* fill(0, 0, 255);
* circle(30, 20, 6); // Visible curve point
* circle(70, 80, 6); // Visible curve point
*
* describe('A curved line between two blue points, with red control points at the ends.');
* }
*
* @method splineProperties
* @return {Object}
*/
fn.splineProperties = function(values) {
return this._renderer.splineProperties(values);
};
/**
* Adds a vertex to a custom shape.
*
* `vertex()` sets the coordinates of vertices drawn between the
* beginShape() and
* endShape() functions.
*
* The first two parameters, `x` and `y`, set the x- and y-coordinates of the
* vertex.
*
* The third parameter, `z`, is optional. It sets the z-coordinate of the
* vertex in WebGL mode. By default, `z` is 0.
*
* The fourth and fifth parameters, `u` and `v`, are also optional. They set
* the u- and v-coordinates for the vertex’s texture when used with
* endShape(). By default, `u` and `v` are both 0.
*
* @method vertex
* @param {Number} x x-coordinate of the vertex.
* @param {Number} y y-coordinate of the vertex.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the shape.
* strokeWeight(3);
*
* // Start drawing the shape.
* // Only draw the vertices.
* beginShape(POINTS);
*
* // Add the vertices.
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Four black dots that form a square are drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* beginShape();
*
* // Add vertices.
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
*
* // Stop drawing the shape.
* endShape(CLOSE);
*
* describe('A white square on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* // Start drawing the shape.
* beginShape();
*
* // Add vertices.
* vertex(-20, -30, 0);
* vertex(35, -30, 0);
* vertex(35, 25, 0);
* vertex(-20, 25, 0);
*
* // Stop drawing the shape.
* endShape(CLOSE);
*
* describe('A white square on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white square spins around slowly on a gray background.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Start drawing the shape.
* beginShape();
*
* // Add vertices.
* vertex(-20, -30, 0);
* vertex(35, -30, 0);
* vertex(35, 25, 0);
* vertex(-20, 25, 0);
*
* // Stop drawing the shape.
* endShape(CLOSE);
* }
*
* @example
* let img;
*
* async function setup() {
* // Load an image to apply as a texture.
* img = await loadImage('assets/laDefense.jpg');
*
* createCanvas(100, 100, WEBGL);
*
* describe('A photograph of a ceiling rotates slowly against a gray background.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Style the shape.
* noStroke();
*
* // Apply the texture.
* texture(img);
* textureMode(NORMAL);
*
* // Start drawing the shape
* beginShape();
*
* // Add vertices.
* vertex(-20, -30, 0, 0, 0);
* vertex(35, -30, 0, 1, 0);
* vertex(35, 25, 0, 1, 1);
* vertex(-20, 25, 0, 0, 1);
*
* // Stop drawing the shape.
* endShape();
* }
*
* @example
* let vid;
* function setup() {
* // Load a video and create a p5.MediaElement object.
* vid = createVideo('/assets/fingers.mov');
* createCanvas(100, 100, WEBGL);
*
* // Hide the video.
* vid.hide();
*
* // Set the video to loop.
* vid.loop();
*
* describe('A rectangle with video as texture');
* }
*
* function draw() {
* background(0);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Set the texture mode.
* textureMode(NORMAL);
*
* // Apply the video as a texture.
* texture(vid);
*
* // Draw a custom shape using uv coordinates.
* beginShape();
* vertex(-40, -40, 0, 0);
* vertex(40, -40, 1, 0);
* vertex(40, 40, 1, 1);
* vertex(-40, 40, 0, 1);
* endShape();
* }
*/
/**
* @method vertex
* @param {Number} x
* @param {Number} y
* @param {Number} [u=0] u-coordinate of the vertex's texture.
* @param {Number} [v=0] v-coordinate of the vertex's texture.
*/
/**
* @method vertex
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @param {Number} [u=0] u-coordinate of the vertex's texture.
* @param {Number} [v=0] v-coordinate of the vertex's texture.
*/
fn.vertex = function(x, y) {
let z, u, v;
// default to (x, y) mode: all other arguments assumed to be 0.
z = u = v = 0;
if (arguments.length === 3) {
// (x, y, z) mode: (u, v) assumed to be 0.
z = arguments[2];
} else if (arguments.length === 4) {
// (x, y, u, v) mode: z assumed to be 0.
u = arguments[2];
v = arguments[3];
} else if (arguments.length === 5) {
// (x, y, z, u, v) mode
z = arguments[2];
u = arguments[3];
v = arguments[4];
}
this._renderer.vertex(x, y, z, u, v);
return;
};
/**
* Begins creating a hole within a flat shape.
*
* The `beginContour()` and endContour()
* functions allow for creating negative space within custom shapes that are
* flat. `beginContour()` begins adding vertices to a negative space and
* endContour() stops adding them.
* `beginContour()` and endContour() must be
* called between beginShape() and
* endShape().
*
* Transformations such as translate(),
* rotate(), and scale()
* don't work between `beginContour()` and
* endContour(). It's also not possible to use
* other shapes, such as ellipse() or
* rect(), between `beginContour()` and
* endContour().
*
* Note: The vertices that define a negative space must "wind" in the opposite
* direction from the outer shape. First, draw vertices for the outer shape
* clockwise order. Then, draw vertices for the negative space in
* counter-clockwise order.
*
* @method beginContour
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* beginShape();
*
* // Exterior vertices, clockwise winding.
* vertex(10, 10);
* vertex(90, 10);
* vertex(90, 90);
* vertex(10, 90);
*
* // Interior vertices, counter-clockwise winding.
* beginContour();
* vertex(30, 30);
* vertex(30, 70);
* vertex(70, 70);
* vertex(70, 30);
* endContour(CLOSE);
*
* // Stop drawing the shape.
* endShape(CLOSE);
*
* describe('A white square with a square hole in its center drawn on a gray background.');
* }
*
* @example
* // Click and drag the mouse to view the scene from different angles.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white square with a square hole in its center drawn on a gray background.');
* }
*
* function draw() {
* background(200);
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Start drawing the shape.
* beginShape();
*
* // Exterior vertices, clockwise winding.
* vertex(-40, -40);
* vertex(40, -40);
* vertex(40, 40);
* vertex(-40, 40);
*
* // Interior vertices, counter-clockwise winding.
* beginContour();
* vertex(-20, -20);
* vertex(-20, 20);
* vertex(20, 20);
* vertex(20, -20);
* endContour(CLOSE);
*
* // Stop drawing the shape.
* endShape(CLOSE);
* }
*/
fn.beginContour = function(kind) {
this._renderer.beginContour(kind);
};
/**
* Stops creating a hole within a flat shape.
*
* The beginContour() and `endContour()`
* functions allow for creating negative space within custom shapes that are
* flat. beginContour() begins adding vertices
* to a negative space and `endContour()` stops adding them.
* beginContour() and `endContour()` must be
* called between beginShape() and
* endShape().
*
* By default,
* the controur has an `OPEN` end, and to close it,
* call `endContour(CLOSE)`. The CLOSE contour mode closes splines smoothly.
*
* Transformations such as translate(),
* rotate(), and scale()
* don't work between beginContour() and
* `endContour()`. It's also not possible to use other shapes, such as
* ellipse() or rect(),
* between beginContour() and `endContour()`.
*
* Note: The vertices that define a negative space must "wind" in the opposite
* direction from the outer shape. First, draw vertices for the outer shape
* clockwise order. Then, draw vertices for the negative space in
* counter-clockwise order.
*
* @method endContour
* @param {OPEN|CLOSE} [mode=OPEN] By default, the value is OPEN
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* beginShape();
*
* // Exterior vertices, clockwise winding.
* vertex(10, 10);
* vertex(90, 10);
* vertex(90, 90);
* vertex(10, 90);
*
* // Interior vertices, counter-clockwise winding.
* beginContour();
* vertex(30, 30);
* vertex(30, 70);
* vertex(70, 70);
* vertex(70, 30);
* endContour(CLOSE);
*
* // Stop drawing the shape.
* endShape(CLOSE);
*
* describe('A white square with a square hole in its center drawn on a gray background.');
* }
*
* @example
* // Click and drag the mouse to view the scene from different angles.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white square with a square hole in its center drawn on a gray background.');
* }
*
* function draw() {
* background(200);
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Start drawing the shape.
* beginShape();
*
* // Exterior vertices, clockwise winding.
* vertex(-40, -40);
* vertex(40, -40);
* vertex(40, 40);
* vertex(-40, 40);
*
* // Interior vertices, counter-clockwise winding.
* beginContour();
* vertex(-20, -20);
* vertex(-20, 20);
* vertex(20, 20);
* vertex(20, -20);
* endContour(CLOSE);
*
* // Stop drawing the shape.
* endShape(CLOSE);
* }
*/
fn.endContour = function(mode = OPEN) {
this._renderer.endContour(mode);
};
}
if (typeof p5 !== 'undefined') {
customShapes(p5, p5.prototype);
}
class States {
#modified = {};
constructor(initialState) {
for (const key in initialState) {
this[key] = initialState[key];
}
}
setValue(key, value) {
if (!(key in this.#modified)) {
this.#modified[key] = this[key];
}
this[key] = value;
}
getDiff() {
const diff = this.#modified;
this.#modified = {};
return diff;
}
getModified() {
return this.#modified;
}
applyDiff(prevModified) {
for (const key in this.#modified) {
this[key] = this.#modified[key];
}
this.#modified = prevModified;
}
}
/**
* @module Rendering
* @submodule Rendering
* @for p5
*/
class ClonableObject {
constructor(obj = {}) {
for (const key in obj) {
this[key] = obj[key];
}
}
clone() {
return new ClonableObject(this);
}
}
class Renderer {
static states = {
strokeColor: null,
strokeSet: false,
fillColor: null,
fillSet: false,
tint: null,
imageMode: CORNER,
rectMode: CORNER,
ellipseMode: CENTER,
strokeWeight: 1,
textFont: { family: 'sans-serif' },
textLeading: 15,
leadingSet: false,
textSize: 12,
textAlign: LEFT,
textBaseline: BASELINE,
bezierOrder: 3,
splineProperties: new ClonableObject({
ends: INCLUDE,
tightness: 0
}),
textWrap: WORD,
// added v2.0
fontStyle: NORMAL, // v1: textStyle
fontStretch: NORMAL,
fontWeight: NORMAL,
lineHeight: NORMAL,
fontVariant: NORMAL,
direction: 'inherit'
};
constructor(pInst, w, h, isMainCanvas) {
this._pInst = pInst;
this._isMainCanvas = isMainCanvas;
this.pixels = [];
this._pixelDensity = Math.ceil(window.devicePixelRatio) || 1;
this.width = w;
this.height = h;
this._events = {};
if (isMainCanvas) {
this._isMainCanvas = true;
}
// Renderer state machine
this.states = new States(Renderer.states);
this.states.strokeColor = new Color([0, 0, 0]);
this.states.fillColor = new Color([1, 1, 1]);
this._pushPopStack = [];
// NOTE: can use the length of the push pop stack instead
this._pushPopDepth = 0;
this._clipping = false;
this._clipInvert = false;
this._currentShape = undefined; // Lazily generate current shape
}
get currentShape() {
if (!this._currentShape) {
this._currentShape = new Shape(this.getCommonVertexProperties());
}
return this._currentShape;
}
remove() {
}
pixelDensity(val){
let returnValue;
if (typeof val === 'number') {
if (val !== this._pixelDensity) {
this._pixelDensity = val;
}
returnValue = this;
this.resize(this.width, this.height);
} else {
returnValue = this._pixelDensity;
}
return returnValue;
}
// Makes a shallow copy of the current states
// and push it into the push pop stack
push() {
this._pushPopDepth++;
this._pushPopStack.push(this.states.getDiff());
}
// Pop the previous states out of the push pop stack and
// assign it back to the current state
pop() {
this._pushPopDepth--;
const diff = this._pushPopStack.pop() || {};
const modified = this.states.getModified();
this.states.applyDiff(diff);
this.updateShapeVertexProperties(modified);
this.updateShapeProperties(modified);
}
bezierOrder(order) {
if (order === undefined) {
return this.states.bezierOrder;
} else {
this.states.setValue('bezierOrder', order);
this.updateShapeProperties();
}
}
bezierVertex(x, y, z = 0, u = 0, v = 0) {
const position = new Vector(x, y, z);
const textureCoordinates = this.getSupportedIndividualVertexProperties()
.textureCoordinates
? new Vector(u, v)
: undefined;
this.currentShape.bezierVertex(position, textureCoordinates);
}
splineProperty(key, value) {
if (value === undefined) {
return this.states.splineProperties[key];
} else {
this.states.setValue('splineProperties', this.states.splineProperties.clone());
this.states.splineProperties[key] = value;
}
this.updateShapeProperties();
}
splineProperties(values) {
if (values) {
for (const key in values) {
this.splineProperty(key, values[key]);
}
} else {
return { ...this.states.splineProperties };
}
}
splineVertex(x, y, z = 0, u = 0, v = 0) {
const position = new Vector(x, y, z);
const textureCoordinates = this.getSupportedIndividualVertexProperties()
.textureCoordinates
? new Vector(u, v)
: undefined;
this.currentShape.splineVertex(position, textureCoordinates);
}
curveDetail(d) {
if (d === undefined) {
return this.states.curveDetail;
} else {
this.states.setValue('curveDetail', d);
}
}
beginShape(...args) {
this.currentShape.reset();
this.updateShapeVertexProperties();
this.currentShape.beginShape(...args);
}
endShape(...args) {
this.currentShape.endShape(...args);
this.drawShape(this.currentShape);
}
beginContour(shapeKind) {
this.currentShape.beginContour(shapeKind);
}
endContour(mode) {
this.currentShape.endContour(mode);
}
drawShape(shape, count) {
throw new Error('Unimplemented');
}
vertex(x, y, z = 0, u = 0, v = 0) {
const position = new Vector(x, y, z);
const textureCoordinates = this.getSupportedIndividualVertexProperties()
.textureCoordinates
? new Vector(u, v)
: undefined;
this.currentShape.vertex(position, textureCoordinates);
}
bezier(x1, y1, x2, y2, x3, y3, x4, y4) {
const oldOrder = this._pInst.bezierOrder();
this._pInst.bezierOrder(oldOrder);
this._pInst.beginShape();
this._pInst.bezierVertex(x1, y1);
this._pInst.bezierVertex(x2, y2);
this._pInst.bezierVertex(x3, y3);
this._pInst.bezierVertex(x4, y4);
this._pInst.endShape();
return this;
}
spline(...args) {
if (args.length === 2 * 4) {
const [x1, y1, x2, y2, x3, y3, x4, y4] = args;
this._pInst.beginShape();
this._pInst.splineVertex(x1, y1);
this._pInst.splineVertex(x2, y2);
this._pInst.splineVertex(x3, y3);
this._pInst.splineVertex(x4, y4);
this._pInst.endShape();
} else if (args.length === 3 * 4) {
const [x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4] = args;
this._pInst.beginShape();
this._pInst.splineVertex(x1, y1, z1);
this._pInst.splineVertex(x2, y2, z2);
this._pInst.splineVertex(x3, y3, z3);
this._pInst.splineVertex(x4, y4, z4);
this._pInst.endShape();
}
return this;
}
beginClip(options = {}) {
if (this._clipping) {
throw new Error("It looks like you're trying to clip while already in the middle of clipping. Did you forget to endClip()?");
}
this._clipping = true;
this._clipInvert = options.invert;
}
endClip() {
if (!this._clipping) {
throw new Error("It looks like you've called endClip() without beginClip(). Did you forget to call beginClip() first?");
}
this._clipping = false;
}
/**
* Resize our canvas element.
*/
resize(w, h) {
this.width = w;
this.height = h;
}
get(x, y, w, h) {
const pd = this._pixelDensity;
const canvas = this.canvas;
if (typeof x === 'undefined' && typeof y === 'undefined') {
// get()
x = y = 0;
w = this.width;
h = this.height;
} else {
x *= pd;
y *= pd;
if (typeof w === 'undefined' && typeof h === 'undefined') {
// get(x,y)
if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) {
return [0, 0, 0, 0];
}
return this._getPixel(x, y);
}
// get(x,y,w,h)
}
const region = new Image(w*pd, h*pd);
region.pixelDensity(pd);
region.canvas
.getContext('2d')
.drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w*pd, h*pd);
return region;
}
scale(x, y){
}
fill(...args) {
this.states.setValue('fillSet', true);
this.states.setValue('fillColor', this._pInst.color(...args));
this.updateShapeVertexProperties();
}
noFill() {
this.states.setValue('fillColor', null);
}
strokeWeight(w) {
if (w === undefined) {
return this.states.strokeWeight;
} else {
this.states.setValue('strokeWeight', w);
}
}
stroke(...args) {
this.states.setValue('strokeSet', true);
this.states.setValue('strokeColor', this._pInst.color(...args));
this.updateShapeVertexProperties();
}
noStroke() {
this.states.setValue('strokeColor', null);
}
getCommonVertexProperties() {
return {};
}
getSupportedIndividualVertexProperties() {
return {
textureCoordinates: false
};
}
updateShapeProperties(modified) {
if (!modified || modified.bezierOrder || modified.splineProperties) {
const shape = this.currentShape;
shape.bezierOrder(this.states.bezierOrder);
shape.splineProperty('ends', this.states.splineProperties.ends);
shape.splineProperty('tightness', this.states.splineProperties.tightness);
}
}
updateShapeVertexProperties(modified) {
const props = this.getCommonVertexProperties();
if (!modified || Object.keys(modified).some(k => k in props)) {
const shape = this.currentShape;
for (const key in props) {
shape[key](props[key]);
}
}
}
_applyDefaults() {
return this;
}
finishDraw() {
// Default no-op implementation
// Override in specific renderers as needed
}
///////////////////////////////
//// TEXT SUPPORT METHODS
//////////////////////////////
_middleAlignOffset = function() {
const { textFont, textSize } = this.states;
const font = textFont?.font;
const ctx = this.textDrawingContext();
const metrics = ctx.measureText('X');
let sCapHeight = (font?.data || {})['OS/2']?.sCapHeight;
if (sCapHeight) {
const unitsPerEm = font.data.head.unitsPerEm;
sCapHeight *= textSize / unitsPerEm;
} else {
sCapHeight = metrics.fontBoundingBoxAscent;
}
return metrics.alphabeticBaseline + sCapHeight / 2;
};
}
function renderer(p5, fn){
/**
* Main graphics and rendering context, as well as the base API
* implementation for p5.js "core". To be used as the superclass for
* Renderer2D and Renderer3D classes, respectively.
*
* @class p5.Renderer
* @param {HTMLElement} elt DOM node that is wrapped
* @param {p5} [pInst] pointer to p5 instance
* @param {Boolean} [isMainCanvas] whether we're using it as main canvas
* @private
*/
p5.Renderer = Renderer;
}
/**
* @module DOM
* @submodule DOM
*/
/**
* @typedef {'video'} VIDEO
* @property {VIDEO} VIDEO
* @final
*/
const VIDEO = 'video';
/**
* @typedef {'audio'} AUDIO
* @property {AUDIO} AUDIO
* @final
*/
const AUDIO = 'audio';
class Cue {
constructor(callback, time, id, val) {
this.callback = callback;
this.time = time;
this.id = id;
this.val = val;
}
}
class MediaElement extends Element {
constructor(elt, pInst) {
super(elt, pInst);
const self = this;
this.elt.crossOrigin = 'anonymous';
this._prevTime = 0;
this._cueIDCounter = 0;
this._cues = [];
this.pixels = [];
this._pixelsState = this;
this._pixelDensity = 1;
this._modified = false;
// Media has an internal canvas that is used when drawing it to the main
// canvas. It will need to be updated each frame as the video itself plays.
// We don't want to update it every time we draw, however, in case the user
// has used load/updatePixels. To handle this, we record the frame drawn to
// the internal canvas so we only update it if the frame has changed.
this._frameOnCanvas = -1;
Object.defineProperty(self, 'src', {
get() {
const firstChildSrc = self.elt.children[0].src;
const srcVal = self.elt.src === window.location.href ? '' : self.elt.src;
const ret =
firstChildSrc === window.location.href ? srcVal : firstChildSrc;
return ret;
},
set(newValue) {
for (let i = 0; i < self.elt.children.length; i++) {
self.elt.removeChild(self.elt.children[i]);
}
const source = document.createElement('source');
source.src = newValue;
elt.appendChild(source);
self.elt.src = newValue;
self._modified = true;
}
});
// private _onended callback, set by the method: onended(callback)
self._onended = function () { };
self.elt.onended = function () {
self._onended(self);
};
}
/**
* Plays audio or video from a media element.
*
* @chainable
*
* @example
* let beat;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display a message.
* text('Click to play', 50, 50);
*
* // Create a p5.MediaElement using createAudio().
* beat = createAudio('assets/beat.mp3');
*
* describe('The text "Click to play" written in black on a gray background. A beat plays when the user clicks the square.');
* }
*
* // Play the beat when the user presses the mouse.
* function mousePressed() {
* beat.play();
* }
*/
play() {
if (this.elt.currentTime === this.elt.duration) {
this.elt.currentTime = 0;
}
let promise;
if (this.elt.readyState > 1) {
promise = this.elt.play();
} else {
// in Chrome, playback cannot resume after being stopped and must reload
this.elt.load();
promise = this.elt.play();
}
if (promise && promise.catch) {
promise.catch(e => {
// if it's an autoplay failure error
if (e.name === 'NotAllowedError') {
if (typeof IS_MINIFIED === 'undefined') {
p5._friendlyAutoplayError(this.src);
} else {
console.error(e);
}
} else {
// any other kind of error
console.error('Media play method encountered an unexpected error', e);
}
});
}
return this;
}
/**
* Stops a media element and sets its current time to 0.
*
* Calling `media.play()` will restart playing audio/video from the beginning.
*
* @chainable
*
* @example
* let beat;
* let isStopped = true;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* beat = createAudio('assets/beat.mp3');
*
* describe('The text "Click to start" written in black on a gray background. The beat starts or stops when the user presses the mouse.');
* }
*
* function draw() {
* background(200);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display different instructions based on playback.
* if (isStopped === true) {
* text('Click to start', 50, 50);
* } else {
* text('Click to stop', 50, 50);
* }
* }
*
* // Adjust playback when the user presses the mouse.
* function mousePressed() {
* if (isStopped === true) {
* // If the beat is stopped, play it.
* beat.play();
* isStopped = false;
* } else {
* // If the beat is playing, stop it.
* beat.stop();
* isStopped = true;
* }
* }
*/
stop() {
this.elt.pause();
this.elt.currentTime = 0;
return this;
}
/**
* Pauses a media element.
*
* Calling `media.play()` will resume playing audio/video from the moment it paused.
*
* @chainable
*
* @example
* let beat;
* let isPaused = true;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* beat = createAudio('assets/beat.mp3');
*
* describe('The text "Click to play" written in black on a gray background. The beat plays or pauses when the user clicks the square.');
* }
*
* function draw() {
* background(200);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display different instructions based on playback.
* if (isPaused === true) {
* text('Click to play', 50, 50);
* } else {
* text('Click to pause', 50, 50);
* }
* }
*
* // Adjust playback when the user presses the mouse.
* function mousePressed() {
* if (isPaused === true) {
* // If the beat is paused,
* // play it.
* beat.play();
* isPaused = false;
* } else {
* // If the beat is playing,
* // pause it.
* beat.pause();
* isPaused = true;
* }
* }
*/
pause() {
this.elt.pause();
return this;
}
/**
* Plays the audio/video repeatedly in a loop.
*
* @chainable
*
* @example
* let beat;
* let isLooping = false;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.MediaElement using createAudio().
* beat = createAudio('assets/beat.mp3');
*
* describe('The text "Click to loop" written in black on a gray background. A beat plays repeatedly in a loop when the user clicks. The beat stops when the user clicks again.');
* }
*
* function draw() {
* background(200);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display different instructions based on playback.
* if (isLooping === true) {
* text('Click to stop', 50, 50);
* } else {
* text('Click to loop', 50, 50);
* }
* }
*
* // Adjust playback when the user presses the mouse.
* function mousePressed() {
* if (isLooping === true) {
* // If the beat is looping, stop it.
* beat.stop();
* isLooping = false;
* } else {
* // If the beat is stopped, loop it.
* beat.loop();
* isLooping = true;
* }
* }
*/
loop() {
this.elt.setAttribute('loop', true);
this.play();
return this;
}
/**
* Stops the audio/video from playing in a loop.
*
* The media will stop when it finishes playing.
*
* @chainable
*
* @example
* let beat;
* let isPlaying = false;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.MediaElement using createAudio().
* beat = createAudio('assets/beat.mp3');
*
* describe('The text "Click to play" written in black on a gray background. A beat plays when the user clicks. The beat stops when the user clicks again.');
* }
*
* function draw() {
* background(200);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display different instructions based on playback.
* if (isPlaying === true) {
* text('Click to stop', 50, 50);
* } else {
* text('Click to play', 50, 50);
* }
* }
*
* // Adjust playback when the user presses the mouse.
* function mousePressed() {
* if (isPlaying === true) {
* // If the beat is playing, stop it.
* beat.stop();
* isPlaying = false;
* } else {
* // If the beat is stopped, play it.
* beat.play();
* isPlaying = true;
* }
* }
*/
noLoop() {
this.elt.removeAttribute('loop');
return this;
}
/**
* Sets up logic to check that autoplay succeeded.
*
* @private
*/
_setupAutoplayFailDetection() {
const timeout = setTimeout(() => {
if (typeof IS_MINIFIED === 'undefined') {
p5._friendlyAutoplayError(this.src);
} else {
console.error(e);
}
}, 500);
this.elt.addEventListener('play', () => clearTimeout(timeout), {
passive: true,
once: true
});
}
/**
* Sets the audio/video to play once it's loaded.
*
* The parameter, `shouldAutoplay`, is optional. Calling
* `media.autoplay()` without an argument causes the media to play
* automatically. If `true` is passed, as in `media.autoplay(true)`, the
* media will automatically play. If `false` is passed, as in
* `media.autoPlay(false)`, it won't play automatically.
*
* @param {Boolean} [shouldAutoplay] whether the element should autoplay.
* @chainable
*
* @example
* let video;
*
* function setup() {
* noCanvas();
*
* // Call handleVideo() once the video loads.
* video = createVideo('assets/fingers.mov', handleVideo);
*
* describe('A video of fingers walking on a treadmill.');
* }
*
* // Set the video's size and play it.
* function handleVideo() {
* video.size(100, 100);
* video.autoplay();
* }
*
* @example
* function setup() {
* noCanvas();
*
* // Load a video, but don't play it automatically.
* let video = createVideo('assets/fingers.mov', handleVideo);
*
* // Play the video when the user clicks on it.
* video.mousePressed(handlePress);
*
* describe('An image of fingers on a treadmill. They start walking when the user double-clicks on them.');
* }
*
* // Set the video's size and playback mode.
* function handleVideo() {
* video.size(100, 100);
* video.autoplay(false);
* }
*
* // Play the video.
* function handleClick() {
* video.play();
* }
*/
autoplay(val) {
const oldVal = this.elt.getAttribute('autoplay');
this.elt.setAttribute('autoplay', val);
// if we turned on autoplay
if (val && !oldVal) {
// bind method to this scope
const setupAutoplayFailDetection =
() => this._setupAutoplayFailDetection();
// if media is ready to play, schedule check now
if (this.elt.readyState === 4) {
setupAutoplayFailDetection();
} else {
// otherwise, schedule check whenever it is ready
this.elt.addEventListener('canplay', setupAutoplayFailDetection, {
passive: true,
once: true
});
}
}
return this;
}
/**
* Sets the audio/video volume.
*
* Calling `media.volume()` without an argument returns the current volume
* as a number in the range 0 (off) to 1 (maximum).
*
* The parameter, `val`, is optional. It's a number that sets the volume
* from 0 (off) to 1 (maximum). For example, calling `media.volume(0.5)`
* sets the volume to half of its maximum.
*
* @return {Number} current volume.
*
* @example
* let dragon;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* dragon = createAudio('assets/lucky_dragons.mp3');
*
* // Show the default media controls.
* dragon.showControls();
*
* describe('The text "Volume: V" on a gray square with media controls beneath it. The number "V" oscillates between 0 and 1 as the music plays.');
* }
*
* function draw() {
* background(200);
*
* // Produce a number between 0 and 1.
* let n = 0.5 * sin(frameCount * 0.01) + 0.5;
*
* // Use n to set the volume.
* dragon.volume(n);
*
* // Get the current volume and display it.
* let v = dragon.volume();
*
* // Round v to 1 decimal place for display.
* v = round(v, 1);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display the volume.
* text(`Volume: ${v}`, 50, 50);
* }
*/
/**
* @param {Number} val volume between 0.0 and 1.0.
* @chainable
*/
volume(val) {
if (typeof val === 'undefined') {
return this.elt.volume;
} else {
this.elt.volume = val;
}
}
/**
* Sets the audio/video playback speed.
*
* The parameter, `val`, is optional. It's a number that sets the playback
* speed. 1 plays the media at normal speed, 0.5 plays it at half speed, 2
* plays it at double speed, and so on. -1 plays the media at normal speed
* in reverse.
*
* Calling `media.speed()` returns the current speed as a number.
*
* Note: Not all browsers support backward playback. Even if they do,
* playback might not be smooth.
*
* @return {Number} current playback speed.
*
* @example
* let dragon;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* dragon = createAudio('assets/lucky_dragons.mp3');
*
* // Show the default media controls.
* dragon.showControls();
*
* describe('The text "Speed: S" on a gray square with media controls beneath it. The number "S" oscillates between 0 and 1 as the music plays.');
* }
*
* function draw() {
* background(200);
*
* // Produce a number between 0 and 2.
* let n = sin(frameCount * 0.01) + 1;
*
* // Use n to set the playback speed.
* dragon.speed(n);
*
* // Get the current speed and display it.
* let s = dragon.speed();
*
* // Round s to 1 decimal place for display.
* s = round(s, 1);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display the speed.
* text(`Speed: ${s}`, 50, 50);
* }
*/
/**
* @param {Number} speed speed multiplier for playback.
* @chainable
*/
speed(val) {
if (typeof val === 'undefined') {
return this.presetPlaybackRate || this.elt.playbackRate;
} else {
if (this.loadedmetadata) {
this.elt.playbackRate = val;
} else {
this.presetPlaybackRate = val;
}
}
}
/**
* Sets the media element's playback time.
*
* The parameter, `time`, is optional. It's a number that specifies the
* time, in seconds, to jump to when playback begins.
*
* Calling `media.time()` without an argument returns the number of seconds
* the audio/video has played.
*
* Note: Time resets to 0 when looping media restarts.
*
* @param {Number} [time] time to jump to (in seconds).
* @return {Number} current time (in seconds).
*
* @example
* let dragon;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* dragon = createAudio('assets/lucky_dragons.mp3');
*
* // Show the default media controls.
* dragon.showControls();
*
* describe('The text "S seconds" on a gray square with media controls beneath it. The number "S" increases as the song plays.');
* }
*
* function draw() {
* background(200);
*
* // Get the current playback time.
* let s = dragon.time();
*
* // Round s to 1 decimal place for display.
* s = round(s, 1);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display the playback time.
* text(`${s} seconds`, 50, 50);
* }
*
* @example
* let dragon;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* dragon = createAudio('assets/lucky_dragons.mp3');
*
* // Show the default media controls.
* dragon.showControls();
*
* // Jump to 2 seconds to start.
* dragon.time(2);
*
* describe('The text "S seconds" on a gray square with media controls beneath it. The number "S" increases as the song plays.');
* }
*
* function draw() {
* background(200);
*
* // Get the current playback time.
* let s = dragon.time();
*
* // Round s to 1 decimal place for display.
* s = round(s, 1);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display the playback time.
* text(`${s} seconds`, 50, 50);
* }
*/
time(val) {
if (typeof val !== 'undefined') {
this.elt.currentTime = val;
}
return this.elt.currentTime;
}
/**
* Returns the audio/video's duration in seconds.
*
* @return {Number} duration (in seconds).
*
* @example
* let dragon;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.MediaElement using createAudio().
* dragon = createAudio('assets/lucky_dragons.mp3');
*
* // Show the default media controls.
* dragon.showControls();
*
* describe('The text "S seconds left" on a gray square with media controls beneath it. The number "S" decreases as the song plays.');
* }
*
* function draw() {
* background(200);
*
* // Calculate the time remaining.
* let s = dragon.duration() - dragon.time();
*
* // Round s to 1 decimal place for display.
* s = round(s, 1);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display the time remaining.
* text(`${s} seconds left`, 50, 50);
* }
*/
duration() {
return this.elt.duration;
}
_ensureCanvas() {
if (!this.canvas) {
this.canvas = document.createElement('canvas');
this.drawingContext = this.canvas.getContext('2d');
this.setModified(true);
}
// Don't update the canvas again if we have already updated the canvas with
// the current frame
const needsRedraw = this._frameOnCanvas !== this._pInst.frameCount;
if (this.loadedmetadata && needsRedraw) {
// wait for metadata for w/h
if (this.canvas.width !== this.elt.width) {
this.canvas.width = this.elt.width;
this.canvas.height = this.elt.height;
this.width = this.canvas.width;
this.height = this.canvas.height;
}
this.drawingContext.clearRect(
0, 0, this.canvas.width, this.canvas.height);
if (this.flipped === true) {
this.drawingContext.save();
this.drawingContext.scale(-1, 1);
this.drawingContext.translate(-this.canvas.width, 0);
}
this.drawingContext.drawImage(
this.elt,
0,
0,
this.canvas.width,
this.canvas.height
);
if (this.flipped === true) {
this.drawingContext.restore();
}
this.setModified(true);
this._frameOnCanvas = this._pInst.frameCount;
}
}
loadPixels(...args) {
this._ensureCanvas();
return p5.Renderer2D.prototype.loadPixels.apply(this, args);
}
updatePixels(x, y, w, h) {
if (this.loadedmetadata) {
// wait for metadata
this._ensureCanvas();
p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
}
this.setModified(true);
return this;
}
get(...args) {
this._ensureCanvas();
return p5.Renderer2D.prototype.get.apply(this, args);
}
_getPixel(...args) {
this.loadPixels();
return p5.Renderer2D.prototype._getPixel.apply(this, args);
}
set(x, y, imgOrCol) {
if (this.loadedmetadata) {
// wait for metadata
this._ensureCanvas();
p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
this.setModified(true);
}
}
copy(...args) {
this._ensureCanvas();
p5.prototype.copy.apply(this, args);
}
mask(...args) {
this.loadPixels();
this.setModified(true);
p5.Image.prototype.mask.apply(this, args);
}
/**
* helper method for web GL mode to figure out if the element
* has been modified and might need to be re-uploaded to texture
* memory between frames.
* @private
* @return {boolean} a boolean indicating whether or not the
* image has been updated or modified since last texture upload.
*/
isModified() {
return this._modified;
}
/**
* helper method for web GL mode to indicate that an element has been
* changed or unchanged since last upload. gl texture upload will
* set this value to false after uploading the texture; or might set
* it to true if metadata has become available but there is no actual
* texture data available yet..
* @param {Boolean} val sets whether or not the element has been
* modified.
* @private
*/
setModified(value) {
this._modified = value;
}
/**
* Calls a function when the audio/video reaches the end of its playback.
*
* The element is passed as an argument to the callback function.
*
* Note: The function won't be called if the media is looping.
*
* @param {Function} callback function to call when playback ends.
* The `p5.MediaElement` is passed as
* the argument.
* @chainable
*
* @example
* let beat;
* let isPlaying = false;
* let isDone = false;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* beat = createAudio('assets/beat.mp3');
*
* // Call handleEnd() when the beat finishes.
* beat.onended(handleEnd);
*
* describe('The text "Click to play" written in black on a gray square. A beat plays when the user clicks. The text "Done!" appears when the beat finishes playing.');
* }
*
* function draw() {
* background(200);
*
* // Style the text.
* textAlign(CENTER);
* textSize(16);
*
* // Display different messages based on playback.
* if (isDone === true) {
* text('Done!', 50, 50);
* } else if (isPlaying === false) {
* text('Click to play', 50, 50);
* } else {
* text('Playing...', 50, 50);
* }
* }
*
* // Play the beat when the user presses the mouse.
* function mousePressed() {
* if (isPlaying === false) {
* isPlaying = true;
* beat.play();
* }
* }
*
* // Set isDone when playback ends.
* function handleEnd() {
* isDone = false;
* }
*/
onended(callback) {
this._onended = callback;
return this;
}
/*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
_getAudioContext() {
return undefined;
}
_getSoundOut() {
return undefined;
}
/**
* Sends the element's audio to an output.
*
* The parameter, `audioNode`, can be an `AudioNode` or an object from the
* `p5.sound` library.
*
* If no element is provided, as in `myElement.connect()`, the element
* connects to the main output. All connections are removed by the
* `.disconnect()` method.
*
* Note: This method is meant to be used with the p5.sound.js addon library.
*
* @param {AudioNode|Object} audioNode AudioNode from the Web Audio API,
* or an object from the p5.sound library
*/
connect(obj) {
let audioContext, mainOutput;
// if p5.sound exists, same audio context
if (this._getAudioContext() && this._getSoundOut()) {
audioContext = this._getAudioContext();
mainOutput = this._getSoundOut().input;
} else {
try {
audioContext = obj.context;
mainOutput = audioContext.destination;
} catch (e) {
throw 'connect() is meant to be used with Web Audio API or p5.sound.js';
}
}
// create a Web Audio MediaElementAudioSourceNode if none already exists
if (!this.audioSourceNode) {
this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
// connect to main output when this method is first called
this.audioSourceNode.connect(mainOutput);
}
// connect to object if provided
if (obj) {
if (obj.input) {
this.audioSourceNode.connect(obj.input);
} else {
this.audioSourceNode.connect(obj);
}
} else {
// otherwise connect to main output of p5.sound / AudioContext
this.audioSourceNode.connect(mainOutput);
}
}
/**
* Disconnect all Web Audio routing, including to the main output.
*
* This is useful if you want to re-route the output through audio effects,
* for example.
*
*/
disconnect() {
if (this.audioSourceNode) {
this.audioSourceNode.disconnect();
} else {
throw 'nothing to disconnect';
}
}
/*** SHOW / HIDE CONTROLS ***/
/**
* Show the default
* HTMLMediaElement
* controls.
*
* Note: The controls vary between web browsers.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background('cornflowerblue');
*
* // Style the text.
* textAlign(CENTER);
* textSize(50);
*
* // Display a dragon.
* text('🐉', 50, 50);
*
* // Create a p5.MediaElement using createAudio().
* let dragon = createAudio('assets/lucky_dragons.mp3');
*
* // Show the default media controls.
* dragon.showControls();
*
* describe('A dragon emoji, 🐉, drawn in the center of a blue square. A song plays in the background. Audio controls are displayed beneath the canvas.');
* }
*/
showControls() {
// must set style for the element to show on the page
this.elt.style['text-align'] = 'inherit';
this.elt.controls = true;
}
/**
* Hide the default
* HTMLMediaElement
* controls.
*
* @example
* let dragon;
* let isHidden = false;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* dragon = createAudio('assets/lucky_dragons.mp3');
*
* // Show the default media controls.
* dragon.showControls();
*
* describe('The text "Double-click to hide controls" written in the middle of a gray square. A song plays in the background. Audio controls are displayed beneath the canvas. The controls appear/disappear when the user double-clicks the square.');
* }
*
* function draw() {
* background(200);
*
* // Style the text.
* textAlign(CENTER);
*
* // Display a different message when controls are hidden or shown.
* if (isHidden === true) {
* text('Double-click to show controls', 10, 20, 80, 80);
* } else {
* text('Double-click to hide controls', 10, 20, 80, 80);
* }
* }
*
* // Show/hide controls based on a double-click.
* function doubleClicked() {
* if (isHidden === true) {
* dragon.showControls();
* isHidden = false;
* } else {
* dragon.hideControls();
* isHidden = true;
* }
* }
*/
hideControls() {
this.elt.controls = false;
}
/**
* Schedules a function to call when the audio/video reaches a specific time
* during its playback.
*
* The first parameter, `time`, is the time, in seconds, when the function
* should run. This value is passed to `callback` as its first argument.
*
* The second parameter, `callback`, is the function to call at the specified
* cue time.
*
* The third parameter, `value`, is optional and can be any type of value.
* `value` is passed to `callback`.
*
* Calling `media.addCue()` returns an ID as a string. This is useful for
* removing the cue later.
*
* @param {Number} time cue time to run the callback function.
* @param {Function} callback function to call at the cue time.
* @param {Object} [value] object to pass as the argument to
* `callback`.
* @return {Number} id ID of this cue,
* useful for `media.removeCue(id)`.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* let beat = createAudio('assets/beat.mp3');
*
* // Play the beat in a loop.
* beat.loop();
*
* // Schedule a few events.
* beat.addCue(0, changeBackground, 'red');
* beat.addCue(2, changeBackground, 'deeppink');
* beat.addCue(4, changeBackground, 'orchid');
* beat.addCue(6, changeBackground, 'lavender');
*
* describe('A red square with a beat playing in the background. Its color changes every 2 seconds while the audio plays.');
* }
*
* // Change the background color.
* function changeBackground(c) {
* background(c);
* }
*/
addCue(time, callback, val) {
const id = this._cueIDCounter++;
const cue = new Cue(callback, time, id, val);
this._cues.push(cue);
if (!this.elt.ontimeupdate) {
this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
}
return id;
}
/**
* Removes a callback based on its ID.
*
* @param {Number} id ID of the cue, created by `media.addCue()`.
*
* @example
* let lavenderID;
* let isRemoved = false;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* let beat = createAudio('assets/beat.mp3');
*
* // Play the beat in a loop.
* beat.loop();
*
* // Schedule a few events.
* beat.addCue(0, changeBackground, 'red');
* beat.addCue(2, changeBackground, 'deeppink');
* beat.addCue(4, changeBackground, 'orchid');
*
* // Record the ID of the "lavender" callback.
* lavenderID = beat.addCue(6, changeBackground, 'lavender');
*
* describe('The text "Double-click to remove lavender." written on a red square. The color changes every 2 seconds while the audio plays. The lavender option is removed when the user double-clicks the square.');
* }
*
* function draw() {
* background(200);
*
* // Display different instructions based on the available callbacks.
* if (isRemoved === false) {
* text('Double-click to remove lavender.', 10, 10, 80, 80);
* } else {
* text('No more lavender.', 10, 10, 80, 80);
* }
* }
*
* // Change the background color.
* function changeBackground(c) {
* background(c);
* }
*
* // Remove the lavender color-change cue when the user double-clicks.
* function doubleClicked() {
* if (isRemoved === false) {
* beat.removeCue(lavenderID);
* isRemoved = true;
* }
* }
*/
removeCue(id) {
for (let i = 0; i < this._cues.length; i++) {
if (this._cues[i].id === id) {
console.log(id);
this._cues.splice(i, 1);
}
}
if (this._cues.length === 0) {
this.elt.ontimeupdate = null;
}
}
/**
* Removes all functions scheduled with `media.addCue()`.
*
* @example
* let isChanging = true;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.MediaElement using createAudio().
* let beat = createAudio('assets/beat.mp3');
*
* // Play the beat in a loop.
* beat.loop();
*
* // Schedule a few events.
* beat.addCue(0, changeBackground, 'red');
* beat.addCue(2, changeBackground, 'deeppink');
* beat.addCue(4, changeBackground, 'orchid');
* beat.addCue(6, changeBackground, 'lavender');
*
* describe('The text "Double-click to stop changing." written on a square. The color changes every 2 seconds while the audio plays. The color stops changing when the user double-clicks the square.');
* }
*
* function draw() {
* background(200);
*
* // Display different instructions based on the available callbacks.
* if (isChanging === true) {
* text('Double-click to stop changing.', 10, 10, 80, 80);
* } else {
* text('No more changes.', 10, 10, 80, 80);
* }
* }
*
* // Change the background color.
* function changeBackground(c) {
* background(c);
* }
*
* // Remove cued functions and stop changing colors when the user
* // double-clicks.
* function doubleClicked() {
* if (isChanging === true) {
* beat.clearCues();
* isChanging = false;
* }
* }
*/
clearCues() {
this._cues = [];
this.elt.ontimeupdate = null;
}
// private method that checks for cues to be fired if events
// have been scheduled using addCue(callback, time).
_onTimeUpdate() {
const playbackTime = this.time();
for (let i = 0; i < this._cues.length; i++) {
const callbackTime = this._cues[i].time;
const val = this._cues[i].val;
if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
// pass the scheduled callbackTime as parameter to the callback
this._cues[i].callback(val);
}
}
this._prevTime = playbackTime;
}
}
// Cue inspired by JavaScript setTimeout, and the
// Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
function media(p5, fn){
/**
* Helpers for create methods.
*/
function addElement(elt, pInst, media) {
const node = pInst._userNode ? pInst._userNode : document.body;
node.appendChild(elt);
const c = media
? new MediaElement(elt, pInst)
: new Element(elt, pInst);
pInst._elements.push(c);
return c;
}
/* VIDEO STUFF */
// Helps perform similar tasks for media element methods.
function createMedia(pInst, type, src, callback) {
const elt = document.createElement(type);
// Create source elements from given sources
src = src || '';
if (typeof src === 'string') {
src = [src];
}
for (const mediaSource of src) {
const sourceEl = document.createElement('source');
sourceEl.setAttribute('src', mediaSource);
elt.appendChild(sourceEl);
}
const mediaEl = addElement(elt, pInst, true);
mediaEl.loadedmetadata = false;
// set width and height onload metadata
elt.addEventListener('loadedmetadata', () => {
mediaEl.width = elt.videoWidth;
mediaEl.height = elt.videoHeight;
// set elt width and height if not set
if (mediaEl.elt.width === 0) mediaEl.elt.width = elt.videoWidth;
if (mediaEl.elt.height === 0) mediaEl.elt.height = elt.videoHeight;
if (mediaEl.presetPlaybackRate) {
mediaEl.elt.playbackRate = mediaEl.presetPlaybackRate;
delete mediaEl.presetPlaybackRate;
}
mediaEl.loadedmetadata = true;
});
// If callback is provided, attach to element
if (typeof callback === 'function') {
const callbackHandler = () => {
callback(mediaEl);
elt.removeEventListener('canplaythrough', callbackHandler);
};
elt.addEventListener('canplaythrough', callbackHandler);
}
return mediaEl;
}
/**
* Creates a `<video>` element for simple audio/video playback.
*
* `createVideo()` returns a new
* p5.MediaElement object. Videos are shown by
* default. They can be hidden by calling `video.hide()` and drawn to the
* canvas using image().
*
* The first parameter, `src`, is the path the video. If a single string is
* passed, as in `'assets/topsecret.mp4'`, a single video is loaded. An array
* of strings can be used to load the same video in different formats. For
* example, `['assets/topsecret.mp4', 'assets/topsecret.ogv', 'assets/topsecret.webm']`.
* This is useful for ensuring that the video can play across different browsers with
* different capabilities. See
* MDN
* for more information about supported formats.
*
* The second parameter, `callback`, is optional. It's a function to call once
* the video is ready to play.
*
* @method createVideo
* @param {String|String[]} [src] path to a video file, or an array of paths for
* supporting different browsers.
* @param {Function} [callback] function to call once the video is ready to play.
* @return {p5.MediaElement} new p5.MediaElement object.
*
* @example
* function setup() {
* noCanvas();
*
* // Load a video and add it to the page.
* // Note: this may not work in some browsers.
* let video = createVideo('assets/small.mp4');
*
* // Show the default video controls.
* video.showControls();
*
* describe('A video of a toy robot with playback controls beneath it.');
* }
*
* @example
* function setup() {
* noCanvas();
*
* // Load a video and add it to the page.
* // Provide an array options for different file formats.
* let video = createVideo(
* ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm']
* );
*
* // Show the default video controls.
* video.showControls();
*
* describe('A video of a toy robot with playback controls beneath it.');
* }
*
* @example
* let video;
*
* function setup() {
* noCanvas();
*
* // Load a video and add it to the page.
* // Provide an array options for different file formats.
* // Call mute() once the video loads.
* video = createVideo(
* ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm'],
* muteVideo
* );
*
* // Show the default video controls.
* video.showControls();
*
* describe('A video of a toy robot with playback controls beneath it.');
* }
*
* // Mute the video once it loads.
* function muteVideo() {
* video.volume(0);
* }
*/
fn.createVideo = function (src, callback) {
// p5._validateParameters('createVideo', arguments);
return createMedia(this, VIDEO, src, callback);
};
/* AUDIO STUFF */
/**
* Creates a hidden `<audio>` element for simple audio playback.
*
* `createAudio()` returns a new
* p5.MediaElement object.
*
* The first parameter, `src`, is the path the audio. If a single string is
* passed, as in `'assets/audio.mp3'`, a single audio is loaded. An array
* of strings can be used to load the same audio in different formats. For
* example, `['assets/audio.mp3', 'assets/video.wav']`.
* This is useful for ensuring that the audio can play across different
* browsers with different capabilities. See
* MDN
* for more information about supported formats.
*
* The second parameter, `callback`, is optional. It's a function to call once
* the audio is ready to play.
*
* @method createAudio
* @param {String|String[]} [src] path to an audio file, or an array of paths
* for supporting different browsers.
* @param {Function} [callback] function to call once the audio is ready to play.
* @return {p5.MediaElement} new p5.MediaElement object.
*
* @example
* function setup() {
* noCanvas();
*
* // Load the audio.
* let beat = createAudio('assets/beat.mp3');
*
* // Show the default audio controls.
* beat.showControls();
*
* describe('An audio beat plays when the user double-clicks the square.');
* }
*/
fn.createAudio = function (src, callback) {
// p5._validateParameters('createAudio', arguments);
return createMedia(this, AUDIO, src, callback);
};
/* CAMERA STUFF */
fn.VIDEO = VIDEO;
fn.AUDIO = AUDIO;
// from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
// Older browsers might not implement mediaDevices at all, so we set an empty object first
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// Some browsers just don't implement it - return a rejected promise with an error
// to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(
new Error('getUserMedia is not implemented in this browser')
);
}
// Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
/**
* Creates a `<video>` element that "captures" the audio/video stream from
* the webcam and microphone.
*
* `createCapture()` returns a new
* p5.MediaElement object. Videos are shown by
* default. They can be hidden by calling `capture.hide()` and drawn to the
* canvas using image().
*
* The first parameter, `type`, is optional. It sets the type of capture to
* use. By default, `createCapture()` captures both audio and video. If `VIDEO`
* is passed, as in `createCapture(VIDEO)`, only video will be captured.
* If `AUDIO` is passed, as in `createCapture(AUDIO)`, only audio will be
* captured. A constraints object can also be passed to customize the stream.
* See the
* W3C documentation for possible properties. Different browsers support different
* properties.
*
* The 'flipped' property is an optional property which can be set to `{flipped:true}`
* to mirror the video output.If it is true then it means that video will be mirrored
* or flipped and if nothing is mentioned then by default it will be `false`.
*
* The second parameter,`callback`, is optional. It's a function to call once
* the capture is ready for use. The callback function should have one
* parameter, `stream`, that's a
* MediaStream object.
*
* Note: `createCapture()` only works when running a sketch locally or using HTTPS. Learn more
* here
* and here.
*
* @method createCapture
* @param {(AUDIO|VIDEO|Object)} [type] type of capture, either AUDIO or VIDEO,
* or a constraints object. Both video and audio
* audio streams are captured by default.
* @param {Object} [flipped] flip the capturing video and mirror the output with `{flipped:true}`. By
* default it is false.
* @param {Function} [callback] function to call once the stream
* has loaded.
* @return {p5.MediaElement} new p5.MediaElement object.
*
* @example
* function setup() {
* noCanvas();
*
* // Create the video capture.
* createCapture(VIDEO);
*
* describe('A video stream from the webcam.');
* }
*
* @example
* let capture;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create the video capture and hide the element.
* capture = createCapture(VIDEO);
* capture.hide();
*
* describe('A video stream from the webcam with inverted colors.');
* }
*
* function draw() {
* // Draw the video capture within the canvas.
* image(capture, 0, 0, width, width * capture.height / capture.width);
*
* // Invert the colors in the stream.
* filter(INVERT);
* }
*
* @example
* let capture;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create the video capture with mirrored output.
* capture = createCapture(VIDEO,{ flipped:true });
* capture.size(100,100);
*
* describe('A video stream from the webcam with flipped or mirrored output.');
* }
*
*
* @example
* function setup() {
* createCanvas(480, 120);
*
* // Create a constraints object.
* let constraints = {
* video: {
* mandatory: {
* minWidth: 1280,
* minHeight: 720
* },
* optional: [{ maxFrameRate: 10 }]
* },
* audio: false
* };
*
* // Create the video capture.
* createCapture(constraints);
*
* describe('A video stream from the webcam.');
* }
*/
fn.createCapture = function (...args) {
// p5._validateParameters('createCapture', args);
// return if getUserMedia is not supported by the browser
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
throw new DOMException('getUserMedia not supported in this browser');
}
let useVideo = true;
let useAudio = true;
let constraints;
let callback;
let flipped = false;
for (const arg of args) {
if (arg === fn.VIDEO) useAudio = false;
else if (arg === fn.AUDIO) useVideo = false;
else if (typeof arg === 'object') {
if (arg.flipped !== undefined) {
flipped = arg.flipped;
delete arg.flipped;
}
constraints = Object.assign({}, constraints, arg);
}
else if (typeof arg === 'function') {
callback = arg;
}
}
const videoConstraints = { video: useVideo, audio: useAudio };
constraints = Object.assign({}, videoConstraints, constraints);
const domElement = document.createElement(VIDEO);
// required to work in iOS 11 & up:
domElement.setAttribute('playsinline', '');
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
try {
if ('srcObject' in domElement) {
domElement.srcObject = stream;
} else {
domElement.src = window.URL.createObjectURL(stream);
}
}
catch (err) {
domElement.src = stream;
}
}).catch(e => {
if (e.name === 'NotFoundError')
p5._friendlyError('No webcam found on this device', 'createCapture');
if (e.name === 'NotAllowedError')
p5._friendlyError('Access to the camera was denied', 'createCapture');
console.error(e);
});
const videoEl = addElement(domElement, this, true);
videoEl.loadedmetadata = false;
// set width and height onload metadata
domElement.addEventListener('loadedmetadata', function () {
domElement.play();
if (domElement.width) {
videoEl.width = domElement.width;
videoEl.height = domElement.height;
if (flipped) {
videoEl.elt.style.transform = 'scaleX(-1)';
}
} else {
videoEl.width = videoEl.elt.width = domElement.videoWidth;
videoEl.height = videoEl.elt.height = domElement.videoHeight;
}
videoEl.loadedmetadata = true;
if (callback) callback(domElement.srcObject);
});
videoEl.flipped = flipped;
return videoEl;
};
// =============================================================================
// p5.MediaElement additions
// =============================================================================
/**
* A class to handle audio and video.
*
* `p5.MediaElement` extends p5.Element with
* methods to handle audio and video. `p5.MediaElement` objects are created by
* calling createVideo,
* createAudio, and
* createCapture.
*
* @class p5.MediaElement
* @param {String} elt DOM node that is wrapped
* @extends p5.Element
*
* @example
* let capture;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createCapture().
* capture = createCapture(VIDEO);
* capture.hide();
*
* describe('A webcam feed with inverted colors.');
* }
*
* function draw() {
* // Display the video stream and invert the colors.
* image(capture, 0, 0, width, width * capture.height / capture.width);
* filter(INVERT);
* }
*/
p5.MediaElement = MediaElement;
// Patch MediaElement to give it access to fn, which p5.sound may attach things to
// if present in a sketch
MediaElement.prototype._getSoundOut = function() {
return p5.soundOut;
};
MediaElement.prototype._getAudioContext = function() {
if (typeof fn.getAudioContext === 'function') {
return fn.getAudioContext();
} else {
return undefined;
}
};
/**
* Path to the media element's source as a string.
*
* @for p5.MediaElement
* @property src
* @return {String} src
* @example
* let beat;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.MediaElement using createAudio().
* beat = createAudio('assets/beat.mp3');
*
* describe('The text "https://p5js.org/reference/assets/beat.mp3" written in black on a gray background.');
* }
*
* function draw() {
* background(200);
*
* textWrap(CHAR);
* text(beat.src, 10, 10, 80, 80);
* }
*/
}
if(typeof p5 !== 'undefined'){
media(p5, p5.prototype);
}
/**
* @requires constants
*/
/*
This function normalizes the first four arguments given to rect, ellipse and arc
according to the mode.
It returns a 'bounding box' object containing the coordinates of the upper left corner (x, y),
and width and height (w, h). The returned width and height are always positive.
*/
function modeAdjust(a, b, c, d, mode) {
let bbox;
if (mode === CORNER) {
// CORNER mode already corresponds to a bounding box (top-left corner, width, height).
// For negative widhts or heights, the absolute value is used.
bbox = {
x: a,
y: b,
w: Math.abs(c),
h: Math.abs(d)
};
} else if (mode === CORNERS) {
// CORNERS mode uses two opposite corners, in any configuration.
// Make sure to get the top left corner by using the minimum of the x and y coordniates.
bbox = {
x: Math.min(a, c),
y: Math.min(b, d),
w: Math.abs(c - a),
h: Math.abs(d - b)
};
} else if (mode === RADIUS) {
// RADIUS mode uses the center point and half the width and height.
// c (half width) and d (half height) could be negative, so use the absolute value
// in calculating the top left corner (x, y).
c = Math.abs(c);
d = Math.abs(d);
bbox = {
x: a - c,
y: b - d,
w: 2 * c,
h: 2 * d
};
} else if (mode === CENTER) {
// CENTER mode uses the center point, width and height.
// c (width) and d (height) could be negative, so use the absolute value
// in calculating the top-left corner (x, y).
c = Math.abs(c);
d = Math.abs(d);
bbox = {
x: a - (c * 0.5),
y: b - (d * 0.5),
w: c,
h: d
};
}
return bbox;
}
var canvas = { modeAdjust };
/**
* @module Shape
* @submodule 2D Primitives
* @for p5
* @requires core
* @requires constants
*/
function primitives(p5, fn){
/**
* This function does 3 things:
*
* 1. Bounds the desired start/stop angles for an arc (in radians) so that:
*
* 0 <= start < TWO_PI ; start <= stop < start + TWO_PI
*
* This means that the arc rendering functions don't have to be concerned
* with what happens if stop is smaller than start, or if the arc 'goes
* round more than once', etc.: they can just start at start and increase
* until stop and the correct arc will be drawn.
*
* 2. Optionally adjusts the angles within each quadrant to counter the naive
* scaling of the underlying ellipse up from the unit circle. Without
* this, the angles become arbitrary when width != height: 45 degrees
* might be drawn at 5 degrees on a 'wide' ellipse, or at 85 degrees on
* a 'tall' ellipse.
*
* 3. Flags up when start and stop correspond to the same place on the
* underlying ellipse. This is useful if you want to do something special
* there (like rendering a whole ellipse instead).
*/
fn._normalizeArcAngles = (
start,
stop,
width,
height,
correctForScaling
) => {
const epsilon = 0.00001; // Smallest visible angle on displays up to 4K.
let separation;
// The order of the steps is important here: each one builds upon the
// adjustments made in the steps that precede it.
// Constrain both start and stop to [0,TWO_PI).
start = start - TWO_PI * Math.floor(start / TWO_PI);
stop = stop - TWO_PI * Math.floor(stop / TWO_PI);
// Get the angular separation between the requested start and stop points.
//
// Technically this separation only matches what gets drawn if
// correctForScaling is enabled. We could add a more complicated calculation
// for when the scaling is uncorrected (in which case the drawn points could
// end up pushed together or pulled apart quite dramatically relative to what
// was requested), but it would make things more opaque for little practical
// benefit.
//
// (If you do disable correctForScaling and find that correspondToSamePoint
// is set too aggressively, the easiest thing to do is probably to just make
// epsilon smaller...)
separation = Math.min(
Math.abs(start - stop),
TWO_PI - Math.abs(start - stop)
);
// Optionally adjust the angles to counter linear scaling.
if (correctForScaling) {
if (start <= HALF_PI) {
start = Math.atan(width / height * Math.tan(start));
} else if (start > HALF_PI && start <= 3 * HALF_PI) {
start = Math.atan(width / height * Math.tan(start)) + PI;
} else {
start = Math.atan(width / height * Math.tan(start)) + TWO_PI;
}
if (stop <= HALF_PI) {
stop = Math.atan(width / height * Math.tan(stop));
} else if (stop > HALF_PI && stop <= 3 * HALF_PI) {
stop = Math.atan(width / height * Math.tan(stop)) + PI;
} else {
stop = Math.atan(width / height * Math.tan(stop)) + TWO_PI;
}
}
// Ensure that start <= stop < start + TWO_PI.
if (start > stop) {
stop += TWO_PI;
}
return {
start,
stop,
correspondToSamePoint: separation < epsilon
};
};
/**
* Draws an arc.
*
* An arc is a section of an ellipse defined by the `x`, `y`, `w`, and
* `h` parameters. `x` and `y` set the location of the arc's center. `w` and
* `h` set the arc's width and height. See
* ellipse() and
* ellipseMode() for more details.
*
* The fifth and sixth parameters, `start` and `stop`, set the angles
* between which to draw the arc. Arcs are always drawn clockwise from
* `start` to `stop`. Angles are always given in radians.
*
* The seventh parameter, `mode`, is optional. It determines the arc's fill
* style. The fill modes are a semi-circle (`OPEN`), a closed semi-circle
* (`CHORD`), or a closed pie segment (`PIE`).
*
* The eighth parameter, `detail`, is also optional. It determines how many
* vertices are used to draw the arc in WebGL mode. The default value is 25.
*
* @method arc
* @param {Number} x x-coordinate of the arc's ellipse.
* @param {Number} y y-coordinate of the arc's ellipse.
* @param {Number} w width of the arc's ellipse by default.
* @param {Number} h height of the arc's ellipse by default.
* @param {Number} start angle to start the arc, specified in radians.
* @param {Number} stop angle to stop the arc, specified in radians.
* @param {(CHORD|PIE|OPEN)} [mode] optional parameter to determine the way of drawing
* the arc. either CHORD, PIE, or OPEN.
* @param {Integer} [detail] optional parameter for WebGL mode only. This is to
* specify the number of vertices that makes up the
* perimeter of the arc. Default value is 25. Won't
* draw a stroke for a detail of more than 50.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* arc(50, 50, 80, 80, 0, PI + HALF_PI);
*
* describe('A white circle on a gray canvas. The top-right quarter of the circle is missing.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* arc(50, 50, 80, 40, 0, PI + HALF_PI);
*
* describe('A white ellipse on a gray canvas. The top-right quarter of the ellipse is missing.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Bottom-right.
* arc(50, 55, 50, 50, 0, HALF_PI);
*
* noFill();
*
* // Bottom-left.
* arc(50, 55, 60, 60, HALF_PI, PI);
*
* // Top-left.
* arc(50, 55, 70, 70, PI, PI + QUARTER_PI);
*
* // Top-right.
* arc(50, 55, 80, 80, PI + QUARTER_PI, TWO_PI);
*
* describe(
* 'A shattered outline of an circle with a quarter of a white circle at the bottom-right.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Default fill mode.
* arc(50, 50, 80, 80, 0, PI + QUARTER_PI);
*
* describe('A white circle with the top-right third missing. The bottom is outlined in black.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // OPEN fill mode.
* arc(50, 50, 80, 80, 0, PI + QUARTER_PI, OPEN);
*
* describe(
* 'A white circle missing a section from the top-right. The bottom is outlined in black.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // CHORD fill mode.
* arc(50, 50, 80, 80, 0, PI + QUARTER_PI, CHORD);
*
* describe('A white circle with a black outline missing a section from the top-right.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // PIE fill mode.
* arc(50, 50, 80, 80, 0, PI + QUARTER_PI, PIE);
*
* describe('A white circle with a black outline. The top-right third is missing.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* // PIE fill mode.
* arc(0, 0, 80, 80, 0, PI + QUARTER_PI, PIE);
*
* describe('A white circle with a black outline. The top-right third is missing.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* // PIE fill mode with 5 vertices.
* arc(0, 0, 80, 80, 0, PI + QUARTER_PI, PIE, 5);
*
* describe('A white circle with a black outline. The top-right third is missing.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* describe('A yellow circle on a black background. The circle opens and closes its mouth.');
* }
*
* function draw() {
* background(0);
*
* // Style the arc.
* noStroke();
* fill(255, 255, 0);
*
* // Update start and stop angles.
* let biteSize = PI / 16;
* let startAngle = biteSize * sin(frameCount * 0.1) + biteSize;
* let endAngle = TWO_PI - startAngle;
*
* // Draw the arc.
* arc(50, 50, 80, 80, startAngle, endAngle, PIE);
* }
*/
fn.arc = function(x, y, w, h, start, stop, mode, detail) {
// this.validate("p5.arc", arguments);
// p5._validateParameters('arc', arguments);
// if the current stroke and fill settings wouldn't result in something
// visible, exit immediately
if (
!this._renderer.states.strokeColor &&
!this._renderer.states.fillColor
) {
return this;
}
if (start === stop) {
return this;
}
start = this._toRadians(start);
stop = this._toRadians(stop);
const vals = canvas.modeAdjust(
x, y,
w, h,
this._renderer.states.ellipseMode
);
const angles = this._normalizeArcAngles(start, stop, vals.w, vals.h, true);
if (angles.correspondToSamePoint) {
// If the arc starts and ends at (near enough) the same place, we choose to
// draw an ellipse instead. This is preferable to faking an ellipse (by
// making stop ever-so-slightly less than start + TWO_PI) because the ends
// join up to each other rather than at a vertex at the centre (leaving
// an unwanted spike in the stroke/fill).
this._renderer.ellipse([vals.x, vals.y, vals.w, vals.h, detail]);
} else {
this._renderer.arc(
vals.x,
vals.y,
vals.w,
vals.h,
angles.start, // [0, TWO_PI)
angles.stop, // [start, start + TWO_PI)
mode,
detail
);
//accessible Outputs
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._accsOutput('arc', [
vals.x,
vals.y,
vals.w,
vals.h,
angles.start,
angles.stop,
mode
]);
}
}
return this;
};
/**
* Draws an ellipse (oval).
*
* An ellipse is a round shape defined by the `x`, `y`, `w`, and
* `h` parameters. `x` and `y` set the location of its center. `w` and
* `h` set its width and height. See
* ellipseMode() for other ways to set
* its position.
*
* If no height is set, the value of width is used for both the width and
* height. If a negative height or width is specified, the absolute value is
* taken.
*
* The fifth parameter, `detail`, is also optional. It determines how many
* vertices are used to draw the ellipse in WebGL mode. The default value is
* 25.
*
* @method ellipse
* @param {Number} x x-coordinate of the center of the ellipse.
* @param {Number} y y-coordinate of the center of the ellipse.
* @param {Number} w width of the ellipse.
* @param {Number} [h] height of the ellipse.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* ellipse(50, 50, 80, 80);
*
* describe('A white circle on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* ellipse(50, 50, 80);
*
* describe('A white circle on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* ellipse(50, 50, 80, 40);
*
* describe('A white ellipse on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* ellipse(0, 0, 80, 40);
*
* describe('A white ellipse on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* // Use 6 vertices.
* ellipse(0, 0, 80, 40, 6);
*
* describe('A white hexagon on a gray canvas.');
* }
*/
/**
* @method ellipse
* @param {Number} x
* @param {Number} y
* @param {Number} w
* @param {Number} h
* @param {Integer} [detail] optional parameter for WebGL mode only. This is to
* specify the number of vertices that makes up the
* perimeter of the ellipse. Default value is 25. Won't
* draw a stroke for a detail of more than 50.
*/
fn.ellipse = function(x, y, w, h, detailX) {
// p5._validateParameters('ellipse', arguments);
return this._renderEllipse(...arguments);
};
/**
* Draws a circle.
*
* A circle is a round shape defined by the `x`, `y`, and `d` parameters.
* `x` and `y` set the location of its center. `d` sets its width and height (diameter).
* Every point on the circle's edge is the same distance, `0.5 * d`, from its center.
* `0.5 * d` (half the diameter) is the circle's radius.
* See ellipseMode() for other ways to set its position.
*
* @method circle
* @param {Number} x x-coordinate of the center of the circle.
* @param {Number} y y-coordinate of the center of the circle.
* @param {Number} d diameter of the circle.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* circle(50, 50, 25);
*
* describe('A white circle with black outline in the middle of a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* circle(0, 0, 25);
*
* describe('A white circle with black outline in the middle of a gray canvas.');
* }
*/
fn.circle = function(...args) {
// p5._validateParameters('circle', args);
const argss = args.slice( 0, 2);
argss.push(args[2], args[2]);
return this._renderEllipse(...argss);
};
// internal method for drawing ellipses (without parameter validation)
fn._renderEllipse = function(x, y, w, h, detailX) {
// if the current stroke and fill settings wouldn't result in something
// visible, exit immediately
if (
!this._renderer.states.strokeColor &&
!this._renderer.states.fillColor
) {
return this;
}
// Duplicate 3rd argument if only 3 given.
if (typeof h === 'undefined') {
h = w;
}
const vals = canvas.modeAdjust(
x, y,
w, h,
this._renderer.states.ellipseMode
);
this._renderer.ellipse([vals.x, vals.y, vals.w, vals.h, detailX]);
//accessible Outputs
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._accsOutput('ellipse', [vals.x, vals.y, vals.w, vals.h]);
}
return this;
};
/**
* Draws a straight line between two points.
*
* A line's default width is one pixel. The version of `line()` with four
* parameters draws the line in 2D. To color a line, use the
* stroke() function. To change its width, use the
* strokeWeight() function. A line
* can't be filled, so the fill() function won't
* affect the line's color.
*
* The version of `line()` with six parameters allows the line to be drawn in
* 3D space. Doing so requires adding the `WEBGL` argument to
* createCanvas().
*
* @method line
* @param {Number} x1 the x-coordinate of the first point.
* @param {Number} y1 the y-coordinate of the first point.
* @param {Number} x2 the x-coordinate of the second point.
* @param {Number} y2 the y-coordinate of the second point.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* line(30, 20, 85, 75);
*
* describe(
* 'A black line on a gray canvas running from top-center to bottom-right.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the line.
* stroke('magenta');
* strokeWeight(5);
*
* line(30, 20, 85, 75);
*
* describe(
* 'A thick, magenta line on a gray canvas running from top-center to bottom-right.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Top.
* line(30, 20, 85, 20);
*
* // Right.
* stroke(126);
* line(85, 20, 85, 75);
*
* // Bottom.
* stroke(255);
* line(85, 75, 30, 75);
*
* describe(
* 'Three lines drawn in grayscale on a gray canvas. They form the top, right, and bottom sides of a square.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* line(-20, -30, 35, 25);
*
* describe(
* 'A black line on a gray canvas running from top-center to bottom-right.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A black line connecting two spheres. The scene spins slowly.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Draw a line.
* line(0, 0, 0, 30, 20, -10);
*
* // Draw the center sphere.
* sphere(10);
*
* // Translate to the second point.
* translate(30, 20, -10);
*
* // Draw the bottom-right sphere.
* sphere(10);
* }
*/
/**
* @method line
* @param {Number} x1
* @param {Number} y1
* @param {Number} z1 the z-coordinate of the first point.
* @param {Number} x2
* @param {Number} y2
* @param {Number} z2 the z-coordinate of the second point.
* @chainable
*/
fn.line = function(...args) {
// p5._validateParameters('line', args);
if (this._renderer.states.strokeColor) {
this._renderer.line(...args);
}
//accessible Outputs
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._accsOutput('line', args);
}
return this;
};
/**
* Draws a single point in space.
*
* A point's default width is one pixel. To color a point, use the
* stroke() function. To change its width, use the
* strokeWeight() function. A point
* can't be filled, so the fill() function won't
* affect the point's color.
*
* The version of `point()` with two parameters allows the point's location to
* be set with its x- and y-coordinates, as in `point(10, 20)`.
*
* The version of `point()` with three parameters allows the point to be drawn
* in 3D space with x-, y-, and z-coordinates, as in `point(10, 20, 30)`.
* Doing so requires adding the `WEBGL` argument to
* createCanvas().
*
* The version of `point()` with one parameter allows the point's location to
* be set with a p5.Vector object.
*
* @method point
* @param {Number} x the x-coordinate.
* @param {Number} y the y-coordinate.
* @param {Number} [z] the z-coordinate (for WebGL mode).
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Making point to 5 pixels
* strokeWeight(5);
*
* // Top-left.
* point(30, 20);
*
* // Top-right.
* point(85, 20);
*
* // Bottom-right.
* point(85, 75);
*
* // Bottom-left.
* point(30, 75);
*
* describe(
* 'Four small, black points drawn on a gray canvas. The points form the corners of a square.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Making point to 5 pixels.
* strokeWeight(5);
*
* // Top-left.
* point(30, 20);
*
* // Top-right.
* point(70, 20);
*
* // Style the next points.
* stroke('purple');
* strokeWeight(10);
*
* // Bottom-right.
* point(70, 80);
*
* // Bottom-left.
* point(30, 80);
*
* describe(
* 'Four points drawn on a gray canvas. Two are black and two are purple. The points form the corners of a square.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Making point to 5 pixels.
* strokeWeight(5);
*
* // Top-left.
* let a = createVector(30, 20);
* point(a);
*
* // Top-right.
* let b = createVector(70, 20);
* point(b);
*
* // Bottom-right.
* let c = createVector(70, 80);
* point(c);
*
* // Bottom-left.
* let d = createVector(30, 80);
* point(d);
*
* describe(
* 'Four small, black points drawn on a gray canvas. The points form the corners of a square.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('Two purple points drawn on a gray canvas.');
* }
*
* function draw() {
* background(200);
*
* // Style the points.
* stroke('purple');
* strokeWeight(10);
*
* // Top-left.
* point(-20, -30);
*
* // Bottom-right.
* point(20, 30);
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('Two purple points drawn on a gray canvas. The scene spins slowly.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Style the points.
* stroke('purple');
* strokeWeight(10);
*
* // Top-left.
* point(-20, -30, 0);
*
* // Bottom-right.
* point(20, 30, -50);
* }
*/
/**
* @method point
* @param {p5.Vector} coordinateVector the coordinate vector.
* @chainable
*/
fn.point = function(...args) {
// p5._validateParameters('point', args);
if (this._renderer.states.strokeColor) {
if (args.length === 1 && args[0] instanceof p5.Vector) {
this._renderer.point.call(
this._renderer,
args[0].x,
args[0].y,
args[0].z
);
} else {
this._renderer.point(...args);
//accessible Outputs
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._accsOutput('point', args);
}
}
}
return this;
};
/**
* Draws a quadrilateral (four-sided shape).
*
* Quadrilaterals include rectangles, squares, rhombuses, and trapezoids. The
* first pair of parameters `(x1, y1)` sets the quad's first point. The next
* three pairs of parameters set the coordinates for its next three points
* `(x2, y2)`, `(x3, y3)`, and `(x4, y4)`. Points should be added in either
* clockwise or counter-clockwise order.
*
* The version of `quad()` with twelve parameters allows the quad to be drawn
* in 3D space. Doing so requires adding the `WEBGL` argument to
* createCanvas().
*
* The thirteenth and fourteenth parameters are optional. In WebGL mode, they
* set the number of segments used to draw the quadrilateral in the x- and
* y-directions. They're both 2 by default.
*
* @method quad
* @param {Number} x1 the x-coordinate of the first point.
* @param {Number} y1 the y-coordinate of the first point.
* @param {Number} x2 the x-coordinate of the second point.
* @param {Number} y2 the y-coordinate of the second point.
* @param {Number} x3 the x-coordinate of the third point.
* @param {Number} y3 the y-coordinate of the third point.
* @param {Number} x4 the x-coordinate of the fourth point.
* @param {Number} y4 the y-coordinate of the fourth point.
* @param {Integer} [detailX] number of segments in the x-direction.
* @param {Integer} [detailY] number of segments in the y-direction.
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* quad(20, 20, 80, 20, 80, 80, 20, 80);
*
* describe('A white square with a black outline drawn on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* quad(20, 30, 80, 30, 80, 70, 20, 70);
*
* describe('A white rectangle with a black outline drawn on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* quad(50, 62, 86, 50, 50, 38, 14, 50);
*
* describe('A white rhombus with a black outline drawn on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* quad(20, 50, 80, 30, 80, 70, 20, 70);
*
* describe('A white trapezoid with a black outline drawn on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* quad(-30, -30, 30, -30, 30, 30, -30, 30);
*
* describe('A white square with a black outline drawn on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A wavy white surface spins around on gray canvas.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Draw the quad.
* quad(-30, -30, 0, 30, -30, 0, 30, 30, 20, -30, 30, -20);
* }
*/
/**
* @method quad
* @param {Number} x1
* @param {Number} y1
* @param {Number} z1 the z-coordinate of the first point.
* @param {Number} x2
* @param {Number} y2
* @param {Number} z2 the z-coordinate of the second point.
* @param {Number} x3
* @param {Number} y3
* @param {Number} z3 the z-coordinate of the third point.
* @param {Number} x4
* @param {Number} y4
* @param {Number} z4 the z-coordinate of the fourth point.
* @param {Integer} [detailX]
* @param {Integer} [detailY]
* @chainable
*/
fn.quad = function(...args) {
// p5._validateParameters('quad', args);
if (this._renderer.states.strokeColor || this._renderer.states.fillColor) {
if (this._renderer.isP3D && args.length < 12) {
// if 3D and we weren't passed 12 args, assume Z is 0
this._renderer.quad.call(
this._renderer,
args[0], args[1], 0,
args[2], args[3], 0,
args[4], args[5], 0,
args[6], args[7], 0,
args[8], args[9]);
} else {
this._renderer.quad(...args);
//accessibile outputs
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._accsOutput('quadrilateral', args);
}
}
}
return this;
};
/**
* Draws a rectangle.
*
* A rectangle is a four-sided shape defined by the `x`, `y`, `w`, and `h`
* parameters. `x` and `y` set the location of its top-left corner. `w` sets
* its width and `h` sets its height. Every angle in the rectangle measures
* 90˚. See rectMode() for other ways to define
* rectangles.
*
* The version of `rect()` with five parameters creates a rounded rectangle. The
* fifth parameter sets the radius for all four corners.
*
* The version of `rect()` with eight parameters also creates a rounded
* rectangle. Each of the last four parameters set the radius of a corner. The
* radii start with the top-left corner and move clockwise around the
* rectangle. If any of these parameters are omitted, they are set to the
* value of the last radius that was set.
*
* @method rect
* @param {Number} x x-coordinate of the rectangle.
* @param {Number} y y-coordinate of the rectangle.
* @param {Number} w width of the rectangle.
* @param {Number} [h] height of the rectangle.
* @param {Number} [tl] optional radius of top-left corner.
* @param {Number} [tr] optional radius of top-right corner.
* @param {Number} [br] optional radius of bottom-right corner.
* @param {Number} [bl] optional radius of bottom-left corner.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* rect(30, 20, 55, 55);
*
* describe('A white square with a black outline on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* rect(30, 20, 55, 40);
*
* describe('A white rectangle with a black outline on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Give all corners a radius of 20.
* rect(30, 20, 55, 50, 20);
*
* describe('A white rectangle with a black outline and round edges on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Give each corner a unique radius.
* rect(30, 20, 55, 50, 20, 15, 10, 5);
*
* describe('A white rectangle with a black outline and round edges of different radii.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* rect(-20, -30, 55, 55);
*
* describe('A white square with a black outline on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white square spins around on gray canvas.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Draw the rectangle.
* rect(-20, -30, 55, 55);
* }
*/
/**
* @method rect
* @param {Number} x
* @param {Number} y
* @param {Number} w
* @param {Number} h
* @param {Integer} [detailX] number of segments in the x-direction (for WebGL mode).
* @param {Integer} [detailY] number of segments in the y-direction (for WebGL mode).
* @chainable
*/
fn.rect = function(...args) {
// p5._validateParameters('rect', args);
return this._renderRect(...args);
};
/**
* Draws a square.
*
* A square is a four-sided shape defined by the `x`, `y`, and `s`
* parameters. `x` and `y` set the location of its top-left corner. `s` sets
* its width and height. Every angle in the square measures 90˚ and all its
* sides are the same length. See rectMode() for
* other ways to define squares.
*
* The version of `square()` with four parameters creates a rounded square.
* The fourth parameter sets the radius for all four corners.
*
* The version of `square()` with seven parameters also creates a rounded
* square. Each of the last four parameters set the radius of a corner. The
* radii start with the top-left corner and move clockwise around the
* square. If any of these parameters are omitted, they are set to the
* value of the last radius that was set.
*
* @method square
* @param {Number} x x-coordinate of the square.
* @param {Number} y y-coordinate of the square.
* @param {Number} s side size of the square.
* @param {Number} [tl] optional radius of top-left corner.
* @param {Number} [tr] optional radius of top-right corner.
* @param {Number} [br] optional radius of bottom-right corner.
* @param {Number} [bl] optional radius of bottom-left corner.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* square(30, 20, 55);
*
* describe('A white square with a black outline in on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Give all corners a radius of 20.
* square(30, 20, 55, 20);
*
* describe(
* 'A white square with a black outline and round edges on a gray canvas.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Give each corner a unique radius.
* square(30, 20, 55, 20, 15, 10, 5);
*
* describe('A white square with a black outline and round edges of different radii.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* square(-20, -30, 55);
*
* describe('A white square with a black outline in on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white square spins around on gray canvas.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Draw the square.
* square(-20, -30, 55);
* }
*/
fn.square = function(x, y, s, tl, tr, br, bl) {
// p5._validateParameters('square', arguments);
// duplicate width for height in case of square
return this._renderRect.call(this, x, y, s, s, tl, tr, br, bl);
};
// internal method to have renderer draw a rectangle
fn._renderRect = function() {
if (this._renderer.states.strokeColor || this._renderer.states.fillColor) {
// duplicate width for height in case only 3 arguments is provided
if (arguments.length === 3) {
arguments[3] = arguments[2];
}
const vals = canvas.modeAdjust(
arguments[0],
arguments[1],
arguments[2],
arguments[3],
this._renderer.states.rectMode
);
// For the default rectMode (CORNER), restore a possible negative width/height
// removed by modeAdjust(). This results in flipped/mirrored rendering,
// which is especially noticable when using WEGBL rendering and texture().
// Note that this behavior only applies to rect(), NOT to ellipse() and arc().
if (this._renderer.states.rectMode === CORNER) {
vals.w = arguments[2];
vals.h = arguments[3];
}
const args = [vals.x, vals.y, vals.w, vals.h];
// append the additional arguments (either cornder radii, or
// segment details) to the argument list
for (let i = 4; i < arguments.length; i++) {
args[i] = arguments[i];
}
this._renderer.rect(args);
//accessible outputs
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._accsOutput('rectangle', [vals.x, vals.y, vals.w, vals.h]);
}
}
return this;
};
/**
* Draws a triangle.
*
* A triangle is a three-sided shape defined by three points. The
* first two parameters specify the triangle's first point `(x1, y1)`. The
* middle two parameters specify its second point `(x2, y2)`. And the last two
* parameters specify its third point `(x3, y3)`.
*
* @method triangle
* @param {Number} x1 x-coordinate of the first point.
* @param {Number} y1 y-coordinate of the first point.
* @param {Number} x2 x-coordinate of the second point.
* @param {Number} y2 y-coordinate of the second point.
* @param {Number} x3 x-coordinate of the third point.
* @param {Number} y3 y-coordinate of the third point.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* triangle(30, 75, 58, 20, 86, 75);
*
* describe('A white triangle with a black outline on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* triangle(-20, 25, 8, -30, 36, 25);
*
* describe('A white triangle with a black outline on a gray canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white triangle spins around on a gray canvas.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Draw the triangle.
* triangle(-20, 25, 8, -30, 36, 25);
* }
*/
fn.triangle = function(...args) {
// p5._validateParameters('triangle', args);
if (this._renderer.states.strokeColor || this._renderer.states.fillColor) {
this._renderer.triangle(args);
}
//accessible outputs
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._accsOutput('triangle', args);
}
return this;
};
}
if(typeof p5 !== 'undefined'){
primitives(p5, p5.prototype);
}
/**
* @module Shape
* @submodule Attributes
* @for p5
* @requires core
* @requires constants
*/
function attributes(p5, fn){
/**
* Changes where ellipses, circles, and arcs are drawn.
*
* By default, the first two parameters of
* ellipse(), circle(),
* and arc()
* are the x- and y-coordinates of the shape's center. The next parameters set
* the shape's width and height. This is the same as calling
* `ellipseMode(CENTER)`.
*
* `ellipseMode(RADIUS)` also uses the first two parameters to set the x- and
* y-coordinates of the shape's center. The next parameters are half of the
* shapes's width and height. Calling `ellipse(0, 0, 10, 15)` draws a shape
* with a width of 20 and height of 30.
*
* `ellipseMode(CORNER)` uses the first two parameters as the upper-left
* corner of the shape. The next parameters are its width and height.
*
* `ellipseMode(CORNERS)` uses the first two parameters as the location of one
* corner of the ellipse's bounding box. The next parameters are the location
* of the opposite corner.
*
* The argument passed to `ellipseMode()` must be written in ALL CAPS because
* the constants `CENTER`, `RADIUS`, `CORNER`, and `CORNERS` are defined this
* way. JavaScript is a case-sensitive language.
*
* @method ellipseMode
* @param {(CENTER|RADIUS|CORNER|CORNERS)} mode either CENTER, RADIUS, CORNER, or CORNERS
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // White ellipse.
* ellipseMode(RADIUS);
* fill(255);
* ellipse(50, 50, 30, 30);
*
* // Gray ellipse.
* ellipseMode(CENTER);
* fill(100);
* ellipse(50, 50, 30, 30);
*
* describe('A white circle with a gray circle at its center. Both circles have black outlines.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // White ellipse.
* ellipseMode(CORNER);
* fill(255);
* ellipse(25, 25, 50, 50);
*
* // Gray ellipse.
* ellipseMode(CORNERS);
* fill(100);
* ellipse(25, 25, 50, 50);
*
* describe('A white circle with a gray circle at its top-left corner. Both circles have black outlines.');
* }
*/
fn.ellipseMode = function(m) {
// p5._validateParameters('ellipseMode', arguments);
if (
m === CORNER ||
m === CORNERS ||
m === RADIUS ||
m === CENTER
) {
this._renderer.states.setValue('ellipseMode', m);
}
return this;
};
/**
* Draws certain features with jagged (aliased) edges.
*
* smooth() is active by default. In 2D mode,
* `noSmooth()` is helpful for scaling up images without blurring. The
* functions don't affect shapes or fonts.
*
* In WebGL mode, `noSmooth()` causes all shapes to be drawn with jagged
* (aliased) edges. The functions don't affect images or fonts.
*
* @method noSmooth
* @chainable
*
* @example
* let heart;
*
* async function setup() {
* // Load a pixelated heart image from an image data string.
* heart = await loadImage('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAEZJREFUGFd9jcsNACAIQ9tB2MeR3YdBMBBq8CIXPi2vBICIiOwkOedatllqWO6Y8yOWoyuNf1GZwgmf+RRG2YXr+xVFmA8HZ9Mx/KGPMtcAAAAASUVORK5CYII=');
* createCanvas(100, 100);
*
* background(50);
*
* // Antialiased hearts.
* image(heart, 10, 10);
* image(heart, 20, 10, 16, 16);
* image(heart, 40, 10, 32, 32);
*
* // Aliased hearts.
* noSmooth();
* image(heart, 10, 60);
* image(heart, 20, 60, 16, 16);
* image(heart, 40, 60, 32, 32);
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* circle(0, 0, 80);
*
* describe('A white circle on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Disable smoothing.
* noSmooth();
*
* background(200);
*
* circle(0, 0, 80);
*
* describe('A pixelated white circle on a gray background.');
* }
*/
fn.noSmooth = function() {
if (!this._renderer.isP3D) {
if ('imageSmoothingEnabled' in this.drawingContext) {
this.drawingContext.imageSmoothingEnabled = false;
}
} else {
this.setAttributes('antialias', false);
}
return this;
};
/**
* Changes where rectangles and squares are drawn.
*
* By default, the first two parameters of
* rect() and square(),
* are the x- and y-coordinates of the shape's upper left corner. The next parameters set
* the shape's width and height. This is the same as calling
* `rectMode(CORNER)`.
*
* `rectMode(CORNERS)` also uses the first two parameters as the location of
* one of the corners. The next parameters are the location of the opposite
* corner. This mode only works for rect().
*
* `rectMode(CENTER)` uses the first two parameters as the x- and
* y-coordinates of the shape's center. The next parameters are its width and
* height.
*
* `rectMode(RADIUS)` also uses the first two parameters as the x- and
* y-coordinates of the shape's center. The next parameters are
* half of the shape's width and height.
*
* The argument passed to `rectMode()` must be written in ALL CAPS because the
* constants `CENTER`, `RADIUS`, `CORNER`, and `CORNERS` are defined this way.
* JavaScript is a case-sensitive language.
*
* @method rectMode
* @param {(CENTER|RADIUS|CORNER|CORNERS)} mode either CORNER, CORNERS, CENTER, or RADIUS
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* rectMode(CORNER);
* fill(255);
* rect(25, 25, 50, 50);
*
* rectMode(CORNERS);
* fill(100);
* rect(25, 25, 50, 50);
*
* describe('A small gray square drawn at the top-left corner of a white square.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* rectMode(RADIUS);
* fill(255);
* rect(50, 50, 30, 30);
*
* rectMode(CENTER);
* fill(100);
* rect(50, 50, 30, 30);
*
* describe('A small gray square drawn at the center of a white square.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* rectMode(CORNER);
* fill(255);
* square(25, 25, 50);
*
* describe('A white square.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* rectMode(RADIUS);
* fill(255);
* square(50, 50, 30);
*
* rectMode(CENTER);
* fill(100);
* square(50, 50, 30);
*
* describe('A small gray square drawn at the center of a white square.');
* }
*/
fn.rectMode = function(m) {
// p5._validateParameters('rectMode', arguments);
if (
m === CORNER ||
m === CORNERS ||
m === RADIUS ||
m === CENTER
) {
this._renderer.states.setValue('rectMode', m);
}
return this; // return current rectMode ?
};
/**
* Draws certain features with smooth (antialiased) edges.
*
* `smooth()` is active by default. In 2D mode,
* noSmooth() is helpful for scaling up images
* without blurring. The functions don't affect shapes or fonts.
*
* In WebGL mode, noSmooth() causes all shapes to
* be drawn with jagged (aliased) edges. The functions don't affect images or
* fonts.
*
* @method smooth
* @chainable
*
* @example
* let heart;
*
* async function setup() {
* // Load a pixelated heart image from an image data string.
* heart = await loadImage('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAEZJREFUGFd9jcsNACAIQ9tB2MeR3YdBMBBq8CIXPi2vBICIiOwkOedatllqWO6Y8yOWoyuNf1GZwgmf+RRG2YXr+xVFmA8HZ9Mx/KGPMtcAAAAASUVORK5CYII=');
*
* createCanvas(100, 100);
*
* background(50);
*
* // Antialiased hearts.
* image(heart, 10, 10);
* image(heart, 20, 10, 16, 16);
* image(heart, 40, 10, 32, 32);
*
* // Aliased hearts.
* noSmooth();
* image(heart, 10, 60);
* image(heart, 20, 60, 16, 16);
* image(heart, 40, 60, 32, 32);
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* circle(0, 0, 80);
*
* describe('A white circle on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Disable smoothing.
* noSmooth();
*
* background(200);
*
* circle(0, 0, 80);
*
* describe('A pixelated white circle on a gray background.');
* }
*/
fn.smooth = function() {
if (!this._renderer.isP3D) {
if ('imageSmoothingEnabled' in this.drawingContext) {
this.drawingContext.imageSmoothingEnabled = true;
}
} else {
this.setAttributes('antialias', true);
}
return this;
};
/**
* Sets the style for rendering the ends of lines.
*
* The caps for line endings are either rounded (`ROUND`), squared
* (`SQUARE`), or extended (`PROJECT`). The default cap is `ROUND`.
*
* The argument passed to `strokeCap()` must be written in ALL CAPS because
* the constants `ROUND`, `SQUARE`, and `PROJECT` are defined this way.
* JavaScript is a case-sensitive language.
*
* @method strokeCap
* @param {(ROUND|SQUARE|PROJECT)} cap either ROUND, SQUARE, or PROJECT
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* strokeWeight(12);
*
* // Top.
* strokeCap(ROUND);
* line(20, 30, 80, 30);
*
* // Middle.
* strokeCap(SQUARE);
* line(20, 50, 80, 50);
*
* // Bottom.
* strokeCap(PROJECT);
* line(20, 70, 80, 70);
*
* describe(
* 'Three horizontal lines. The top line has rounded ends, the middle line has squared ends, and the bottom line has longer, squared ends.'
* );
* }
*/
fn.strokeCap = function(cap) {
// p5._validateParameters('strokeCap', arguments);
if (
cap === ROUND ||
cap === SQUARE ||
cap === PROJECT
) {
this._renderer.strokeCap(cap);
}
return this;
};
/**
* Sets the style of the joints that connect line segments.
*
* Joints are either mitered (`MITER`), beveled (`BEVEL`), or rounded
* (`ROUND`). The default joint is `MITER` in 2D mode and `ROUND` in WebGL
* mode.
*
* The argument passed to `strokeJoin()` must be written in ALL CAPS because
* the constants `MITER`, `BEVEL`, and `ROUND` are defined this way.
* JavaScript is a case-sensitive language.
*
* @method strokeJoin
* @param {(MITER|BEVEL|ROUND)} join either MITER, BEVEL, or ROUND
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the line.
* noFill();
* strokeWeight(10);
* strokeJoin(MITER);
*
* // Draw the line.
* beginShape();
* vertex(35, 20);
* vertex(65, 50);
* vertex(35, 80);
* endShape();
*
* describe('A right-facing arrowhead shape with a pointed tip in center of canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the line.
* noFill();
* strokeWeight(10);
* strokeJoin(BEVEL);
*
* // Draw the line.
* beginShape();
* vertex(35, 20);
* vertex(65, 50);
* vertex(35, 80);
* endShape();
*
* describe('A right-facing arrowhead shape with a flat tip in center of canvas.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the line.
* noFill();
* strokeWeight(10);
* strokeJoin(ROUND);
*
* // Draw the line.
* beginShape();
* vertex(35, 20);
* vertex(65, 50);
* vertex(35, 80);
* endShape();
*
* describe('A right-facing arrowhead shape with a rounded tip in center of canvas.');
* }
*/
fn.strokeJoin = function(join) {
// p5._validateParameters('strokeJoin', arguments);
if (
join === ROUND ||
join === BEVEL ||
join === MITER
) {
this._renderer.strokeJoin(join);
}
return this;
};
/**
* Sets the width of the stroke used for points, lines, and the outlines of
* shapes.
*
* Note: `strokeWeight()` is affected by transformations, especially calls to
* scale().
*
* @method strokeWeight
* @param {Number} weight the weight of the stroke (in pixels).
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Top.
* line(20, 20, 80, 20);
*
* // Middle.
* strokeWeight(4);
* line(20, 40, 80, 40);
*
* // Bottom.
* strokeWeight(10);
* line(20, 70, 80, 70);
*
* describe('Three horizontal black lines. The top line is thin, the middle is medium, and the bottom is thick.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Top.
* line(20, 20, 80, 20);
*
* // Scale by a factor of 5.
* scale(5);
*
* // Bottom. Coordinates are adjusted for scaling.
* line(4, 8, 16, 8);
*
* describe('Two horizontal black lines. The top line is thin and the bottom is five times thicker than the top.');
* }
*/
fn.strokeWeight = function(w) {
// p5._validateParameters('strokeWeight', arguments);
this._renderer.strokeWeight(w);
return this;
};
}
if(typeof p5 !== 'undefined'){
attributes(p5, p5.prototype);
}
/**
* @module Shape
* @submodule Curves
* @for p5
* @requires core
*/
function curves(p5, fn){
/**
* Draws a Bézier curve.
*
* Bézier curves can form shapes and curves that slope gently. They're defined
* by two anchor points and two control points. Bézier curves provide more
* control than the spline curves created with the
* spline() function.
*
* The first two parameters, `x1` and `y1`, set the first anchor point. The
* first anchor point is where the curve starts.
*
* The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two control
* points. The control points "pull" the curve towards them.
*
* The seventh and eighth parameters, `x4` and `y4`, set the last anchor
* point. The last anchor point is where the curve ends.
*
* Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
* `bezier()` has twelve arguments because each point has x-, y-,
* and z-coordinates.
*
* @method bezier
* @param {Number} x1 x-coordinate of the first anchor point.
* @param {Number} y1 y-coordinate of the first anchor point.
* @param {Number} x2 x-coordinate of the first control point.
* @param {Number} y2 y-coordinate of the first control point.
* @param {Number} x3 x-coordinate of the second control point.
* @param {Number} y3 y-coordinate of the second control point.
* @param {Number} x4 x-coordinate of the second anchor point.
* @param {Number} y4 y-coordinate of the second anchor point.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Draw the anchor points in black.
* stroke(0);
* strokeWeight(5);
* point(85, 20);
* point(15, 80);
*
* // Draw the control points in red.
* stroke(255, 0, 0);
* point(10, 10);
* point(90, 90);
*
* // Draw a black bezier curve.
* noFill();
* stroke(0);
* strokeWeight(1);
* bezier(85, 20, 10, 10, 90, 90, 15, 80);
*
* // Draw red lines from the anchor points to the control points.
* stroke(255, 0, 0);
* line(85, 20, 10, 10);
* line(15, 80, 90, 90);
*
* describe(
* 'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
* );
* }
*
* @example
* // Click the mouse near the red dot in the top-left corner
* // and drag to change the curve's shape.
*
* let x2 = 10;
* let y2 = 10;
* let isChanging = false;
*
* function setup() {
* createCanvas(100, 100);
*
* describe(
* 'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
* );
* }
*
* function draw() {
* background(200);
*
* // Draw the anchor points in black.
* stroke(0);
* strokeWeight(5);
* point(85, 20);
* point(15, 80);
*
* // Draw the control points in red.
* stroke(255, 0, 0);
* point(x2, y2);
* point(90, 90);
*
* // Draw a black bezier curve.
* noFill();
* stroke(0);
* strokeWeight(1);
* bezier(85, 20, x2, y2, 90, 90, 15, 80);
*
* // Draw red lines from the anchor points to the control points.
* stroke(255, 0, 0);
* line(85, 20, x2, y2);
* line(15, 80, 90, 90);
* }
*
* // Start changing the first control point if the user clicks near it.
* function mousePressed() {
* if (dist(mouseX, mouseY, x2, y2) < 20) {
* isChanging = true;
* }
* }
*
* // Stop changing the first control point when the user releases the mouse.
* function mouseReleased() {
* isChanging = false;
* }
*
* // Update the first control point while the user drags the mouse.
* function mouseDragged() {
* if (isChanging === true) {
* x2 = mouseX;
* y2 = mouseY;
* }
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background('skyblue');
*
* // Draw the red balloon.
* fill('red');
* bezier(50, 60, 5, 15, 95, 15, 50, 60);
*
* // Draw the balloon string.
* line(50, 60, 50, 80);
*
* describe('A red balloon in a blue sky.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A red balloon in a blue sky. The balloon rotates slowly, revealing that it is flat.');
* }
*
* function draw() {
* background('skyblue');
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Draw the red balloon.
* fill('red');
* bezier(0, 0, 0, -45, -45, 0, 45, -45, 0, 0, 0, 0);
*
* // Draw the balloon string.
* line(0, 0, 0, 0, 20, 0);
* }
*/
/**
* @method bezier
* @param {Number} x1
* @param {Number} y1
* @param {Number} z1 z-coordinate of the first anchor point.
* @param {Number} x2
* @param {Number} y2
* @param {Number} z2 z-coordinate of the first control point.
* @param {Number} x3
* @param {Number} y3
* @param {Number} z3 z-coordinate of the second control point.
* @param {Number} x4
* @param {Number} y4
* @param {Number} z4 z-coordinate of the second anchor point.
* @chainable
*/
fn.bezier = function(...args) {
// p5._validateParameters('bezier', args);
// if the current stroke and fill settings wouldn't result in something
// visible, exit immediately
if (
!this._renderer.states.strokeColor &&
!this._renderer.states.fillColor
) {
return this;
}
this._renderer.bezier(...args);
return this;
};
/**
* Calculates coordinates along a Bézier curve using interpolation.
*
* `bezierPoint()` calculates coordinates along a Bézier curve using the
* anchor and control points. It expects points in the same order as the
* bezier() function. `bezierPoint()` works one axis
* at a time. Passing the anchor and control points' x-coordinates will
* calculate the x-coordinate of a point on the curve. Passing the anchor and
* control points' y-coordinates will calculate the y-coordinate of a point on
* the curve.
*
* The first parameter, `a`, is the coordinate of the first anchor point.
*
* The second and third parameters, `b` and `c`, are the coordinates of the
* control points.
*
* The fourth parameter, `d`, is the coordinate of the last anchor point.
*
* The fifth parameter, `t`, is the amount to interpolate along the curve. 0
* is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
* between them.
*
* @method bezierPoint
* @param {Number} a coordinate of first anchor point.
* @param {Number} b coordinate of first control point.
* @param {Number} c coordinate of second control point.
* @param {Number} d coordinate of second anchor point.
* @param {Number} t amount to interpolate between 0 and 1.
* @return {Number} coordinate of the point on the curve.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the coordinates for the curve's anchor and control points.
* let x1 = 85;
* let x2 = 10;
* let x3 = 90;
* let x4 = 15;
* let y1 = 20;
* let y2 = 10;
* let y3 = 90;
* let y4 = 80;
*
* // Style the curve.
* noFill();
*
* // Draw the curve.
* bezier(x1, y1, x2, y2, x3, y3, x4, y4);
*
* // Draw circles along the curve's path.
* fill(255);
*
* // Top-right.
* let x = bezierPoint(x1, x2, x3, x4, 0);
* let y = bezierPoint(y1, y2, y3, y4, 0);
* circle(x, y, 5);
*
* // Center.
* x = bezierPoint(x1, x2, x3, x4, 0.5);
* y = bezierPoint(y1, y2, y3, y4, 0.5);
* circle(x, y, 5);
*
* // Bottom-left.
* x = bezierPoint(x1, x2, x3, x4, 1);
* y = bezierPoint(y1, y2, y3, y4, 1);
* circle(x, y, 5);
*
* describe('A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* describe('A black s-curve on a gray square. A white circle moves back and forth along the curve.');
* }
*
* function draw() {
* background(200);
*
* // Set the coordinates for the curve's anchor and control points.
* let x1 = 85;
* let x2 = 10;
* let x3 = 90;
* let x4 = 15;
* let y1 = 20;
* let y2 = 10;
* let y3 = 90;
* let y4 = 80;
*
* // Draw the curve.
* noFill();
* bezier(x1, y1, x2, y2, x3, y3, x4, y4);
*
* // Calculate the circle's coordinates.
* let t = 0.5 * sin(frameCount * 0.01) + 0.5;
* let x = bezierPoint(x1, x2, x3, x4, t);
* let y = bezierPoint(y1, y2, y3, y4, t);
*
* // Draw the circle.
* fill(255);
* circle(x, y, 5);
* }
*/
fn.bezierPoint = function(a, b, c, d, t) {
// p5._validateParameters('bezierPoint', arguments);
const adjustedT = 1 - t;
return (
Math.pow(adjustedT, 3) * a +
3 * Math.pow(adjustedT, 2) * t * b +
3 * adjustedT * Math.pow(t, 2) * c +
Math.pow(t, 3) * d
);
};
/**
* Calculates coordinates along a line that's tangent to a Bézier curve.
*
* Tangent lines skim the surface of a curve. A tangent line's slope equals
* the curve's slope at the point where it intersects.
*
* `bezierTangent()` calculates coordinates along a tangent line using the
* Bézier curve's anchor and control points. It expects points in the same
* order as the bezier() function. `bezierTangent()`
* works one axis at a time. Passing the anchor and control points'
* x-coordinates will calculate the x-coordinate of a point on the tangent
* line. Passing the anchor and control points' y-coordinates will calculate
* the y-coordinate of a point on the tangent line.
*
* The first parameter, `a`, is the coordinate of the first anchor point.
*
* The second and third parameters, `b` and `c`, are the coordinates of the
* control points.
*
* The fourth parameter, `d`, is the coordinate of the last anchor point.
*
* The fifth parameter, `t`, is the amount to interpolate along the curve. 0
* is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
* between them.
*
* @method bezierTangent
* @param {Number} a coordinate of first anchor point.
* @param {Number} b coordinate of first control point.
* @param {Number} c coordinate of second control point.
* @param {Number} d coordinate of second anchor point.
* @param {Number} t amount to interpolate between 0 and 1.
* @return {Number} coordinate of a point on the tangent line.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the coordinates for the curve's anchor and control points.
* let x1 = 85;
* let x2 = 10;
* let x3 = 90;
* let x4 = 15;
* let y1 = 20;
* let y2 = 10;
* let y3 = 90;
* let y4 = 80;
*
* // Style the curve.
* noFill();
*
* // Draw the curve.
* bezier(x1, y1, x2, y2, x3, y3, x4, y4);
*
* // Draw tangents along the curve's path.
* fill(255);
*
* // Top-right circle.
* stroke(0);
* let x = bezierPoint(x1, x2, x3, x4, 0);
* let y = bezierPoint(y1, y2, y3, y4, 0);
* circle(x, y, 5);
*
* // Top-right tangent line.
* // Scale the tangent point to draw a shorter line.
* stroke(255, 0, 0);
* let tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0);
* let ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0);
* line(x + tx, y + ty, x - tx, y - ty);
*
* // Center circle.
* stroke(0);
* x = bezierPoint(x1, x2, x3, x4, 0.5);
* y = bezierPoint(y1, y2, y3, y4, 0.5);
* circle(x, y, 5);
*
* // Center tangent line.
* // Scale the tangent point to draw a shorter line.
* stroke(255, 0, 0);
* tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0.5);
* ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0.5);
* line(x + tx, y + ty, x - tx, y - ty);
*
* // Bottom-left circle.
* stroke(0);
* x = bezierPoint(x1, x2, x3, x4, 1);
* y = bezierPoint(y1, y2, y3, y4, 1);
* circle(x, y, 5);
*
* // Bottom-left tangent.
* // Scale the tangent point to draw a shorter line.
* stroke(255, 0, 0);
* tx = 0.1 * bezierTangent(x1, x2, x3, x4, 1);
* ty = 0.1 * bezierTangent(y1, y2, y3, y4, 1);
* line(x + tx, y + ty, x - tx, y - ty);
*
* describe(
* 'A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles. Red tangent lines extend from the white circles.'
* );
* }
*/
fn.bezierTangent = function(a, b, c, d, t) {
// p5._validateParameters('bezierTangent', arguments);
const adjustedT = 1 - t;
return (
3 * d * Math.pow(t, 2) -
3 * c * Math.pow(t, 2) +
6 * c * adjustedT * t -
6 * b * adjustedT * t +
3 * b * Math.pow(adjustedT, 2) -
3 * a * Math.pow(adjustedT, 2)
);
};
/**
* Draws a curve using a Catmull-Rom spline.
*
* Spline curves can form shapes and curves that slope gently. They’re like
* cables that are attached to a set of points. By default (`ends: INCLUDE`),
* the curve passes through all four points you provide, in order
* `p0(x1,y1)` -> `p1(x2,y2)` -> `p2(x3,y3)` -> `p3(x4,y4)`. Think of them as
* points on a curve. If you switch to `ends: EXCLUDE`, p0 and p3 act
* like control points and only the middle span `p1->p2` is drawn.
*
* Spline curves can also be drawn in 3D using WebGL mode. The 3D version of
* `spline()` has twelve arguments because each point has x-, y-, and
* z-coordinates.
*
* @method spline
* @param {Number} x1 x-coordinate of point p0.
* @param {Number} y1 y-coordinate of point p0.
* @param {Number} x2 x-coordinate of point p1.
* @param {Number} y2 y-coordinate of point p1.
* @param {Number} x3 x-coordinate of point p2.
* @param {Number} y3 y-coordinate of point p2.
* @param {Number} x4 x-coordinate of point p3.
* @param {Number} y4 y-coordinate of point p3.
* @chainable
*
* @example
* function setup() {
* createCanvas(200, 200);
* background(240);
* noFill();
*
* stroke(0);
* strokeWeight(2);
* spline(40, 60, 100, 40, 120, 120, 60, 140);
*
* strokeWeight(5);
* point(40, 60);
* point(100, 40);
* point(120, 120);
* point(60, 140);
*
* describe('A black spline passes smoothly through four points');
* }
*
* @example
* function setup() {
* createCanvas(200, 200);
* background(245);
*
* // Ensure the curve includes both end spans p0->p1 and p2->p3
* splineProperty('ends', INCLUDE);
*
* // Control / anchor points
* const p0 = createVector(30, 160);
* const p1 = createVector(60, 40);
* const p2 = createVector(140, 40);
* const p3 = createVector(170, 160);
*
* // Draw the spline that passes through ALL four points (INCLUDE)
* noFill();
* stroke(0);
* strokeWeight(2);
* spline(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
*
* // Draw markers + labels
* fill(255);
* stroke(0);
* const r = 6;
* circle(p0.x, p0.y, r);
* circle(p1.x, p1.y, r);
* circle(p2.x, p2.y, r);
* circle(p3.x, p3.y, r);
*
* noStroke();
* fill(0);
* text('p0', p0.x - 14, p0.y + 14);
* text('p1', p1.x - 14, p1.y - 8);
* text('p2', p2.x + 4, p2.y - 8);
* text('p3', p3.x + 4, p3.y + 14);
*
* describe('A black Catmull-Rom spline passes through p0, p1, p2, p3 with endpoints included.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
* splineProperty('ends', EXCLUDE);
*
* // Draw a black spline curve.
* noFill();
* strokeWeight(1);
* stroke(0);
* spline(5, 26, 73, 24, 73, 61, 15, 65);
*
* // Draw red spline curves from the points.
* stroke(255, 0, 0);
* spline(5, 26, 5, 26, 73, 24, 73, 61);
* spline(73, 24, 73, 61, 15, 65, 15, 65);
*
* // Draw the points in black.
* strokeWeight(5);
* stroke(0);
* point(73, 24);
* point(73, 61);
*
* // Draw the points in red.
* stroke(255, 0, 0);
* point(5, 26);
* point(15, 65);
*
* describe(
* 'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
* );
* }
*
* @example
* let x1 = 5;
* let y1 = 26;
* let isChanging = false;
*
* function setup() {
* createCanvas(100, 100);
*
* describe(
* 'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
* );
* }
*
* function draw() {
* background(200);
*
* // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
* splineProperty('ends', EXCLUDE);
*
* // Draw a black spline curve.
* noFill();
* strokeWeight(1);
* stroke(0);
* spline(x1, y1, 73, 24, 73, 61, 15, 65);
*
* // Draw red spline curves from the points.
* stroke(255, 0, 0);
* spline(x1, y1, x1, y1, 73, 24, 73, 61);
* spline(73, 24, 73, 61, 15, 65, 15, 65);
*
* // Draw the anchor points in black.
* strokeWeight(5);
* stroke(0);
* point(73, 24);
* point(73, 61);
*
* // Draw the points in red.
* stroke(255, 0, 0);
* point(x1, y1);
* point(15, 65);
* }
*
* // Start changing the first point if the user clicks near it.
* function mousePressed() {
* if (dist(mouseX, mouseY, x1, y1) < 20) {
* isChanging = true;
* }
* }
*
* // Stop changing the first point when the user releases the mouse.
* function mouseReleased() {
* isChanging = false;
* }
*
* // Update the first point while the user drags the mouse.
* function mouseDragged() {
* if (isChanging === true) {
* x1 = mouseX;
* y1 = mouseY;
* }
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background('skyblue');
*
* // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
* splineProperty('ends', EXCLUDE);
*
* // Draw the red balloon.
* fill('red');
* spline(-150, 275, 50, 60, 50, 60, 250, 275);
*
* // Draw the balloon string.
* line(50, 60, 50, 80);
*
* describe('A red balloon in a blue sky.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A red balloon in a blue sky.');
* }
*
* function draw() {
* background('skyblue');
*
* // Exclude the ends—skip the outer spans (p0→p1 and p2→p3) so only the middle span (p1→p2) is drawn.
* splineProperty('ends', EXCLUDE);
*
* // Rotate around the y-axis.
* rotateY(frameCount * 0.01);
*
* // Draw the red balloon.
* fill('red');
* spline(-200, 225, 0, 0, 10, 0, 0, 10, 0, 200, 225, 0);
*
* // Draw the balloon string.
* line(0, 10, 0, 0, 30, 0);
* }
*/
/**
* @method spline
* @param {Number} x1
* @param {Number} y1
* @param {Number} z1 z-coordinate of point p0.
* @param {Number} x2
* @param {Number} y2
* @param {Number} z2 z-coordinate of point p1.
* @param {Number} x3
* @param {Number} y3
* @param {Number} z3 z-coordinate of point p2.
* @param {Number} x4
* @param {Number} y4
* @param {Number} z4 z-coordinate of point p3.
* @chainable
*/
fn.spline = function(...args) {
if (
!this._renderer.states.strokeColor &&
!this._renderer.states.fillColor
) {
return this;
}
this._renderer.spline(...args);
return this;
};
/**
* Calculates coordinates along a spline curve using interpolation.
*
* `splinePoint()` calculates coordinates along a spline curve using four
* points p0, p1, p2, p3. It expects points in the same order as the
* spline() function. `splinePoint()` works one axis
* at a time. Passing the points' x-coordinates will
* calculate the x-coordinate of a point on the curve. Passing the
* points' y-coordinates will calculate the y-coordinate of a point on
* the curve.
*
* The first parameter, `a`, is the coordinate of point p0.
*
* The second and third parameters, `b` and `c`, are the coordinates of
* points p1 and p2.
*
* The fourth parameter, `d`, is the coordinate of point p3.
*
* The fifth parameter, `t`, is the amount to interpolate along the span
* from p1 to p2. `t = 0` is p1, `t = 1` is p2, and `t = 0.5` is halfway
* between them.
*
* @method splinePoint
* @param {Number} a coordinate of point p0.
* @param {Number} b coordinate of point p1.
* @param {Number} c coordinate of point p2.
* @param {Number} d coordinate of point p3.
* @param {Number} t amount to interpolate between 0 and 1.
* @return {Number} coordinate of a point on the curve.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
*
* // Set the coordinates for the curve's four points (p0, p1, p2, p3).
* let x1 = 5;
* let y1 = 26;
* let x2 = 73;
* let y2 = 24;
* let x3 = 73;
* let y3 = 61;
* let x4 = 15;
* let y4 = 65;
*
* // Draw the curve.
* noFill();
* spline(x1, y1, x2, y2, x3, y3, x4, y4);
*
* // Draw circles along the curve's path.
* fill(255);
*
* // Top.
* let x = splinePoint(x1, x2, x3, x4, 0);
* let y = splinePoint(y1, y2, y3, y4, 0);
* circle(x, y, 5);
*
* // Center.
* x = splinePoint(x1, x2, x3, x4, 0.5);
* y = splinePoint(y1, y2, y3, y4, 0.5);
* circle(x, y, 5);
*
* // Bottom.
* x = splinePoint(x1, x2, x3, x4, 1);
* y = splinePoint(y1, y2, y3, y4, 1);
* circle(x, y, 5);
*
* describe('A black curve on a gray square. The endpoints and center of the curve are marked with white circles.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* describe('A black curve on a gray square. A white circle moves back and forth along the curve.');
* }
*
* function draw() {
* background(200);
*
* // Set the coordinates for the curve's four points (p0, p1, p2, p3).
* let x1 = 5;
* let y1 = 26;
* let x2 = 73;
* let y2 = 24;
* let x3 = 73;
* let y3 = 61;
* let x4 = 15;
* let y4 = 65;
*
* // Draw the curve.
* noFill();
* spline(x1, y1, x2, y2, x3, y3, x4, y4);
*
* // Calculate the circle's coordinates.
* let t = 0.5 * sin(frameCount * 0.01) + 0.5;
* let x = splinePoint(x1, x2, x3, x4, t);
* let y = splinePoint(y1, y2, y3, y4, t);
*
* // Draw the circle.
* fill(255);
* circle(x, y, 5);
* }
*
* @example
* let p0, p1, p2, p3;
*
* function setup() {
* createCanvas(200, 200);
* splineProperty('ends', INCLUDE); // make endpoints part of the curve
*
* // Four points forming a gentle arch
* p0 = createVector(30, 160);
* p1 = createVector(60, 50);
* p2 = createVector(140, 50);
* p3 = createVector(170, 160);
*
* describe('Black spline through p0–p3. A red dot marks the location at parameter t on p1->p2 using splinePoint.');
* }
*
* function draw() {
* background(245);
*
* // Draw the spline for context
* noFill();
* stroke(0);
* strokeWeight(2);
* spline(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
*
* // Map mouse X to t in [0, 1] (span p1->p2)
* let t = constrain(map(mouseX, 0, width, 0, 1), 0, 1);
*
* // Evaluate the curve point by axis (splinePoint works one axis at a time)
* let x = splinePoint(p0.x, p1.x, p2.x, p3.x, t);
* let y = splinePoint(p0.y, p1.y, p2.y, p3.y, t);
*
* // Marker at the evaluated position
* noStroke();
* fill('red');
* circle(x, y, 8);
*
* // Draw control/anchor points
* stroke(0);
* strokeWeight(1);
* fill(255);
* const r = 6;
* circle(p0.x, p0.y, r);
* circle(p1.x, p1.y, r);
* circle(p2.x, p2.y, r);
* circle(p3.x, p3.y, r);
*
* // Labels + UI hint
* noStroke();
* fill(20);
* textSize(10);
* text('p0', p0.x - 12, p0.y + 14);
* text('p1', p1.x - 12, p1.y - 8);
* text('p2', p2.x + 4, p2.y - 8);
* text('p3', p3.x + 4, p3.y + 14);
* text('t = ' + nf(t, 1, 2) + ' (p1→p2)', 8, 16);
* }
*/
fn.splinePoint = function(a, b, c, d, t) {
const s = this._renderer.states.splineProperties.tightness,
t3 = t * t * t,
t2 = t * t,
f1 = (s - 1) / 2 * t3 + (1 - s) * t2 + (s - 1) / 2 * t,
f2 = (s + 3) / 2 * t3 + (-5 - s) / 2 * t2 + 1.0,
f3 = (-3 - s) / 2 * t3 + (s + 2) * t2 + (1 - s) / 2 * t,
f4 = (1 - s) / 2 * t3 + (s - 1) / 2 * t2;
return a * f1 + b * f2 + c * f3 + d * f4;
};
/**
* Calculates coordinates along a line that's tangent to a spline curve.
*
* Tangent lines skim the surface of a curve. A tangent line's slope equals
* the curve's slope at the point where it intersects.
*
* `splineTangent()` calculates coordinates along a tangent line using four
* points p0, p1, p2, p3. It expects points in the same order as the
* spline() function. `splineTangent()` works one
* axis at a time. Passing the points' x-coordinates returns the x-component of
* the tangent vector; passing the points' y-coordinates returns the y-component.
* The first parameter, `a`, is the coordinate of point p0.
*
* The second and third parameters, `b` and `c`, are the coordinates of
* points p1 and p2.
*
* The fourth parameter, `d`, is the coordinate of point p3.
*
* The fifth parameter, `t`, is the amount to interpolate along the span
* from p1 to p2. `t = 0` is p1, `t = 1` is p2, and `t = 0.5` is halfway
* between them.
*
* @method splineTangent
* @param {Number} a coordinate of point p0.
* @param {Number} b coordinate of point p1.
* @param {Number} c coordinate of point p2.
* @param {Number} d coordinate of point p3.
* @param {Number} t amount to interpolate between 0 and 1.
* @return {Number} coordinate of a point on the tangent line.
*
* @example
* function setup() {
* createCanvas(120, 120);
* describe('A black spline on a gray canvas. A red dot moves along the curve on its own. A short line shows the tangent direction at the dot.');
* }
*
* function draw() {
* background(240);
*
* const x1 = 15, y1 = 40;
* const x2 = 90, y2 = 25;
* const x3 = 95, y3 = 95;
* const x4 = 30, y4 = 110;
*
* noFill();
* stroke(0);
* strokeWeight(2);
* spline(x1, y1, x2, y2, x3, y3, x4, y4);
*
* const t = 0.5 + 0.5 * sin(frameCount * 0.03);
*
* const px = splinePoint(x1, x2, x3, x4, t);
* const py = splinePoint(y1, y2, y3, y4, t);
*
* let tx = splineTangent(x1, x2, x3, x4, t);
* let ty = splineTangent(y1, y2, y3, y4, t);
*
* const m = Math.hypot(tx, ty) || 1;
* tx = (tx / m) * 16;
* ty = (ty / m) * 16;
*
* stroke(0);
* strokeWeight(2);
* line(px, py, px + tx, py + ty);
*
* noStroke();
* fill('red');
* circle(px, py, 7);
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the coordinates for the curve's four points (p0, p1, p2, p3).
* let x1 = 5;
* let y1 = 26;
* let x2 = 73;
* let y2 = 24;
* let x3 = 73;
* let y3 = 61;
* let x4 = 15;
* let y4 = 65;
*
* // Draw the curve.
* noFill();
* spline(x1, y1, x2, y2, x3, y3, x4, y4);
*
* // Draw tangents along the curve's path.
* fill(255);
*
* // Top circle.
* stroke(0);
* let x = splinePoint(x1, x2, x3, x4, 0);
* let y = splinePoint(y1, y2, y3, y4, 0);
* circle(x, y, 5);
*
* // Top tangent line.
* // Scale the tangent point to draw a shorter line.
* stroke(255, 0, 0);
* let tx = 0.2 * splineTangent(x1, x2, x3, x4, 0);
* let ty = 0.2 * splineTangent(y1, y2, y3, y4, 0);
* line(x + tx, y + ty, x - tx, y - ty);
*
* // Center circle.
* stroke(0);
* x = splinePoint(x1, x2, x3, x4, 0.5);
* y = splinePoint(y1, y2, y3, y4, 0.5);
* circle(x, y, 5);
*
* // Center tangent line.
* // Scale the tangent point to draw a shorter line.
* stroke(255, 0, 0);
* tx = 0.2 * splineTangent(x1, x2, x3, x4, 0.5);
* ty = 0.2 * splineTangent(y1, y2, y3, y4, 0.5);
* line(x + tx, y + ty, x - tx, y - ty);
*
* // Bottom circle.
* stroke(0);
* x = splinePoint(x1, x2, x3, x4, 1);
* y = splinePoint(y1, y2, y3, y4, 1);
* circle(x, y, 5);
*
* // Bottom tangent line.
* // Scale the tangent point to draw a shorter line.
* stroke(255, 0, 0);
* tx = 0.2 * splineTangent(x1, x2, x3, x4, 1);
* ty = 0.2 * splineTangent(y1, y2, y3, y4, 1);
* line(x + tx, y + ty, x - tx, y - ty);
*
* describe(
* 'A black curve on a gray square. A white circle moves back and forth along the curve.'
* );
* }
*/
fn.splineTangent = function(a, b, c, d, t) {
const s = this._renderer.states.splineProperties.tightness,
tt3 = t * t * 3,
t2 = t * 2,
f1 = (s - 1) / 2 * tt3 + (1 - s) * t2 + (s - 1) / 2,
f2 = (s + 3) / 2 * tt3 + (-5 - s) / 2 * t2,
f3 = (-3 - s) / 2 * tt3 + (s + 2) * t2 + (1 - s) / 2,
f4 = (1 - s) / 2 * tt3 + (s - 1) / 2 * t2;
return a * f1 + b * f2 + c * f3 + d * f4;
};
}
if(typeof p5 !== 'undefined'){
curves(p5, p5.prototype);
}
/**
* @module Shape
* @submodule Custom Shapes
* @for p5
* @requires core
* @requires constants
*/
function vertex(p5, fn){
/**
* Begins adding vertices to a custom shape.
*
* The `beginShape()` and endShape() functions
* allow for creating custom shapes in 2D or 3D. `beginShape()` begins adding
* vertices to a custom shape and endShape() stops
* adding them.
*
* The parameter, `kind`, sets the kind of shape to make. The available kinds are:
*
* - `PATH` (the default) to draw shapes by tracing out the path along their edges.
* - `POINTS` to draw a series of points.
* - `LINES` to draw a series of unconnected line segments.
* - `TRIANGLES` to draw a series of separate triangles.
* - `TRIANGLE_FAN` to draw a series of connected triangles sharing the first vertex in a fan-like fashion.
* - `TRIANGLE_STRIP` to draw a series of connected triangles in strip fashion.
* - `QUADS` to draw a series of separate quadrilaterals (quads).
* - `QUAD_STRIP` to draw quad strip using adjacent edges to form the next quad.
*
* After calling `beginShape()`, shapes can be built by calling
* vertex(),
* bezierVertex(), and/or
* splineVertex(). Calling
* endShape() will stop adding vertices to the
* shape. Each shape will be outlined with the current stroke color and filled
* with the current fill color.
*
* Transformations such as translate(),
* rotate(), and
* scale() don't work between `beginShape()` and
* endShape(). It's also not possible to use
* other shapes, such as ellipse() or
* rect(), between `beginShape()` and
* endShape().
*
* @method beginShape
* @param {(POINTS|LINES|TRIANGLES|TRIANGLE_FAN|TRIANGLE_STRIP|QUADS|QUAD_STRIP|PATH)} [kind=PATH] either POINTS, LINES, TRIANGLES, TRIANGLE_FAN
* TRIANGLE_STRIP, QUADS, QUAD_STRIP or PATH. Defaults to PATH.
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* beginShape();
*
* // Add vertices.
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
*
* // Stop drawing the shape.
* endShape(CLOSE);
*
* describe('A white square on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* // Only draw the vertices (points).
* beginShape(POINTS);
*
* // Add vertices.
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Four black dots that form a square are drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* // Only draw lines between alternating pairs of vertices.
* beginShape(LINES);
*
* // Add vertices.
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Two horizontal black lines on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the shape.
* noFill();
*
* // Start drawing the shape.
* beginShape();
*
* // Add vertices.
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Three black lines form a sideways U shape on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the shape.
* noFill();
*
* // Start drawing the shape.
* beginShape();
*
* // Add vertices.
* vertex(30, 20);
* vertex(85, 20);
* vertex(85, 75);
* vertex(30, 75);
*
* // Stop drawing the shape.
* // Connect the first and last vertices.
* endShape(CLOSE);
*
* describe('A black outline of a square drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* // Draw a series of triangles.
* beginShape(TRIANGLES);
*
* // Left triangle.
* vertex(30, 75);
* vertex(40, 20);
* vertex(50, 75);
*
* // Right triangle.
* vertex(60, 20);
* vertex(70, 75);
* vertex(80, 20);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Two white triangles drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* // Draw a series of triangles.
* beginShape(TRIANGLE_STRIP);
*
* // Add vertices.
* vertex(30, 75);
* vertex(40, 20);
* vertex(50, 75);
* vertex(60, 20);
* vertex(70, 75);
* vertex(80, 20);
* vertex(90, 75);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Five white triangles that are interleaved drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* // Draw a series of triangles that share their first vertex.
* beginShape(TRIANGLE_FAN);
*
* // Add vertices.
* vertex(57.5, 50);
* vertex(57.5, 15);
* vertex(92, 50);
* vertex(57.5, 85);
* vertex(22, 50);
* vertex(57.5, 15);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Four white triangles form a square are drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* // Draw a series of quadrilaterals.
* beginShape(QUADS);
*
* // Left rectangle.
* vertex(30, 20);
* vertex(30, 75);
* vertex(50, 75);
* vertex(50, 20);
*
* // Right rectangle.
* vertex(65, 20);
* vertex(65, 75);
* vertex(85, 75);
* vertex(85, 20);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Two white rectangles drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* // Draw a series of quadrilaterals.
* beginShape(QUAD_STRIP);
*
* // Add vertices.
* vertex(30, 20);
* vertex(30, 75);
* vertex(50, 20);
* vertex(50, 75);
* vertex(65, 20);
* vertex(65, 75);
* vertex(85, 20);
* vertex(85, 75);
*
* // Stop drawing the shape.
* endShape();
*
* describe('Three white rectangles that share edges are drawn on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* // Start drawing the shape.
* // Draw a series of quadrilaterals.
* beginShape(PATH);
*
* // Add the vertices.
* vertex(-30, -30, 0);
* vertex(30, -30, 0);
* vertex(30, -10, 0);
* vertex(-10, -10, 0);
* vertex(-10, 10, 0);
* vertex(30, 10, 0);
* vertex(30, 30, 0);
* vertex(-30, 30, 0);
*
* // Stop drawing the shape.
* // Connect the first and last vertices.
* endShape(CLOSE);
*
* describe('A blocky C shape drawn in white on a gray background.');
* }
*
* @example
* // Click and drag with the mouse to view the scene from different angles.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A blocky C shape drawn in red, blue, and green on a gray background.');
* }
*
* function draw() {
* background(200);
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Start drawing the shape.
* // Draw a series of quadrilaterals.
* beginShape(PATH);
*
* // Add the vertices.
* fill('red');
* stroke('red');
* vertex(-30, -30, 0);
* vertex(30, -30, 0);
* vertex(30, -10, 0);
* fill('green');
* stroke('green');
* vertex(-10, -10, 0);
* vertex(-10, 10, 0);
* vertex(30, 10, 0);
* fill('blue');
* stroke('blue');
* vertex(30, 30, 0);
* vertex(-30, 30, 0);
*
* // Stop drawing the shape.
* // Connect the first and last vertices.
* endShape(CLOSE);
* }
*/
fn.beginShape = function(kind) {
// p5._validateParameters('beginShape', arguments);
this._renderer.beginShape(...arguments);
};
/**
* Adds a Bézier curve segment to a custom shape.
*
* `bezierVertex()` adds a curved segment to custom shapes. The Bézier curves
* it creates are defined like those made by the
* bezier() function. `bezierVertex()` must be
* called between the
* beginShape() and
* endShape() functions.
* Bézier need a starting point. Building a shape
* only with Bézier curves needs one initial
* call to bezierVertex(), before
* a number of `bezierVertex()` calls that is a multiple of the parameter
* set by bezierOrder(...) (default 3).
* But shapes can mix different types of vertices, so if there
* are some previous vertices, then the initial anchor is not needed,
* only the multiples of 3 (or the Bézier order) calls to
* `bezierVertex` for each curve.
*
* Each curve of order 3 requires three calls to `bezierVertex`, so
* 2 curves would need 7 calls to `bezierVertex()`:
* (1 one initial anchor point, two sets of 3 curves describing the curves)
* With `bezierOrder(2)`, two curves would need 5 calls: 1 + 2 + 2.
*
* Bézier curves can also be drawn in 3D using WebGL mode.
*
* Note: `bezierVertex()` won’t work when an argument is passed to
* beginShape().
*
* @method bezierVertex
* @param {Number} x x-coordinate of the first control point.
* @param {Number} y y-coordinate of the first control point.
* @param {Number} [u]
* @param {Number} [v]
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the shape.
* noFill();
*
* // Start drawing the shape.
* beginShape();
*
* // Add the first anchor point.
* bezierVertex(30, 20);
*
* // Add the Bézier vertex.
* bezierVertex(80, 0);
* bezierVertex(80, 75);
* bezierVertex(30, 75);
*
* // Stop drawing the shape.
* endShape();
*
* describe('A black C curve on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Draw the anchor points in black.
* stroke(0);
* strokeWeight(5);
* point(30, 20);
* point(30, 75);
*
* // Draw the control points in red.
* stroke(255, 0, 0);
* point(80, 0);
* point(80, 75);
*
* // Style the shape.
* noFill();
* stroke(0);
* strokeWeight(1);
*
* // Start drawing the shape.
* beginShape();
*
* // Add the first anchor point.
* bezierVertex(30, 20);
*
* // Add the Bézier vertex.
* bezierVertex(80, 0);
* bezierVertex(80, 75);
* bezierVertex(30, 75);
*
* // Stop drawing the shape.
* endShape();
*
* // Draw red lines from the anchor points to the control points.
* stroke(255, 0, 0);
* line(30, 20, 80, 0);
* line(30, 75, 80, 75);
*
* describe(
* 'A gray square with three curves. A black curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
* );
* }
*
* @example
* // Click the mouse near the red dot in the top-right corner
* // and drag to change the curve's shape.
*
* let x2 = 80;
* let y2 = 0;
* let isChanging = false;
*
* function setup() {
* createCanvas(100, 100);
*
* describe(
* 'A gray square with three curves. A black curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
* );
* }
*
* function draw() {
* background(200);
*
* // Draw the anchor points in black.
* stroke(0);
* strokeWeight(5);
* point(30, 20);
* point(30, 75);
*
* // Draw the control points in red.
* stroke(255, 0, 0);
* point(x2, y2);
* point(80, 75);
*
* // Style the shape.
* noFill();
* stroke(0);
* strokeWeight(1);
*
* // Start drawing the shape.
* beginShape();
*
* // Add the first anchor point.
* bezierVertex(30, 20);
*
* // Add the Bézier vertex.
* bezierVertex(x2, y2);
* bezierVertex(80, 75);
* bezierVertex(30, 75);
*
* // Stop drawing the shape.
* endShape();
*
* // Draw red lines from the anchor points to the control points.
* stroke(255, 0, 0);
* line(30, 20, x2, y2);
* line(30, 75, 80, 75);
* }
*
* // Start changing the first control point if the user clicks near it.
* function mousePressed() {
* if (dist(mouseX, mouseY, x2, y2) < 20) {
* isChanging = true;
* }
* }
*
* // Stop changing the first control point when the user releases the mouse.
* function mouseReleased() {
* isChanging = false;
* }
*
* // Update the first control point while the user drags the mouse.
* function mouseDragged() {
* if (isChanging === true) {
* x2 = mouseX;
* y2 = mouseY;
* }
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Start drawing the shape.
* beginShape();
*
* // Add the first anchor point.
* bezierVertex(30, 20);
*
* // Add the Bézier vertices.
* bezierVertex(80, 0);
* bezierVertex(80, 75);
* bezierVertex(30, 75);
*
* bezierVertex(50, 80);
* bezierVertex(60, 25);
* bezierVertex(30, 20);
*
* // Stop drawing the shape.
* endShape();
*
* describe('A crescent moon shape drawn in white on a gray background.');
* }
*
* @example
* // Click and drag the mouse to view the scene from different angles.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A crescent moon shape drawn in white on a blue background. When the user drags the mouse, the scene rotates and a second moon is revealed.');
* }
*
* function draw() {
* background('midnightblue');
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Style the moons.
* noStroke();
* fill('lemonchiffon');
*
* // Draw the first moon.
* beginShape();
* bezierVertex(-20, -30, 0);
*
* bezierVertex(30, -50, 0);
* bezierVertex(30, 25, 0);
* bezierVertex(-20, 25, 0);
*
* bezierVertex(0, 30, 0);
* bezierVertex(10, -25, 0);
* bezierVertex(-20, -30, 0);
* endShape();
*
* // Draw the second moon.
* beginShape();
*
* bezierVertex(-20, -30, -20);
*
* bezierVertex(30, -50, -20);
* bezierVertex(30, 25, -20);
* bezierVertex(-20, 25, -20);
*
* bezierVertex(0, 30, -20);
* bezierVertex(10, -25, -20);
* bezierVertex(-20, -30, -20);
*
* endShape();
* }
*/
/**
* @method bezierVertex
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @param {Number} [u]
* @param {Number} [v]
*/
fn.bezierVertex = function(...args) {
this._renderer.bezierVertex(...args);
};
/**
* Concludes the vertices of a custom shape.
*
* The beginShape() and `endShape()` functions
* allow for creating custom shapes in 2D or 3D.
* beginShape() begins adding vertices to a
* custom shape and `endShape()` stops adding them.
*
* The first parameter, `mode`, is optional. By default, the first and last
* vertices of a shape aren't connected. If the constant `CLOSE` is passed, as
* in `endShape(CLOSE)`, then the first and last vertices will be connected.
* When CLOSE mode is used for splines (with `splineVeertex()`), the shape is ended smoothly.
*
*
* The second parameter, `count`, is also optional. In WebGL mode, it’s more
* efficient to draw many copies of the same shape using a technique called
* instancing.
* The `count` parameter tells WebGL mode how many copies to draw. For
* example, calling `endShape(CLOSE, 400)` after drawing a custom shape will
* make it efficient to draw 400 copies. This feature requires
* writing a custom shader.
*
* After calling beginShape(), shapes can be
* built by calling vertex(),
* bezierVertex() and/or
* splineVertex(). Calling
* `endShape()` will stop adding vertices to the
* shape. Each shape will be outlined with the current stroke color and filled
* with the current fill color.
*
* Transformations such as translate(),
* rotate(), and
* scale() don't work between
* beginShape() and `endShape()`. It's also not
* possible to use other shapes, such as ellipse() or
* rect(), between
* beginShape() and `endShape()`.
*
* @method endShape
* @param {CLOSE} [mode] use CLOSE to close the shape
* @param {Integer} [count] number of times you want to draw/instance the shape (for WebGL mode).
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Style the shapes.
* noFill();
*
* // Left triangle.
* beginShape();
* vertex(20, 20);
* vertex(45, 20);
* vertex(45, 80);
* endShape(CLOSE);
*
* // Right triangle.
* beginShape();
* vertex(50, 20);
* vertex(75, 20);
* vertex(75, 80);
* endShape();
*
* describe(
* 'Two sets of black lines drawn on a gray background. The three lines on the left form a right triangle. The two lines on the right form a right angle.'
* );
* }
*
* @example
* function setup() {
* createCanvas(200, 100);
*
* background(240);
*
* noFill();
* stroke(0);
*
* // Open shape (left)
* beginShape();
* vertex(20, 20);
* vertex(80, 20);
* vertex(80, 80);
* endShape(); // Not closed
*
* // Closed shape (right)
* beginShape();
* vertex(120, 20);
* vertex(180, 20);
* vertex(180, 80);
* endShape(CLOSE); // Closed
*
* describe(
* 'Two right-angled shapes on a light gray background. The left shape is open with three lines. The right shape is closed, forming a triangle.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
* background(200);
*
* beginShape();
*
* splineVertex(32, 91);
* splineVertex(21, 17);
* splineVertex(68, 19);
* splineVertex(82, 91);
*
* endShape(CLOSE);
*
* describe(
* 'A curvy four-sided slightly lopsided blob.'
* );
* }
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* // Create a string with the vertex shader program.
* // The vertex shader is called for each vertex.
* let vertSrc = `#version 300 es
*
* precision mediump float;
*
* in vec3 aPosition;
* flat out int instanceID;
*
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* void main() {
*
* // Copy the instance ID to the fragment shader.
* instanceID = gl_InstanceID;
* vec4 positionVec4 = vec4(aPosition, 1.0);
*
* // gl_InstanceID represents a numeric value for each instance.
* // Using gl_InstanceID allows us to move each instance separately.
* // Here we move each instance horizontally by ID * 23.
* float xOffset = float(gl_InstanceID) * 23.0;
*
* // Apply the offset to the final position.
* gl_Position = uProjectionMatrix * uModelViewMatrix * (positionVec4 -
* vec4(xOffset, 0.0, 0.0, 0.0));
* }
* `;
*
* // Create a string with the fragment shader program.
* // The fragment shader is called for each pixel.
* let fragSrc = `#version 300 es
*
* precision mediump float;
*
* out vec4 outColor;
* flat in int instanceID;
* uniform float numInstances;
*
* void main() {
* vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
* vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
*
* // Normalize the instance ID.
* float normId = float(instanceID) / numInstances;
*
* // Mix between two colors using the normalized instance ID.
* outColor = mix(red, blue, normId);
* }
* `;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Create a p5.Shader object.
* let myShader = createShader(vertSrc, fragSrc);
*
* background(220);
*
* // Compile and apply the p5.Shader.
* shader(myShader);
*
* // Set the numInstances uniform.
* myShader.setUniform('numInstances', 4);
*
* // Translate the origin to help align the drawing.
* translate(25, -10);
*
* // Style the shapes.
* noStroke();
*
* // Draw the shapes.
* beginShape();
* vertex(0, 0);
* vertex(0, 20);
* vertex(20, 20);
* vertex(20, 0);
* vertex(0, 0);
* endShape(CLOSE, 4);
*
* describe('A row of four squares. Their colors transition from purple on the left to red on the right');
* }
*/
fn.endShape = function(mode, count = 1) {
// p5._validateParameters('endShape', arguments);
if (count < 1) {
console.log('🌸 p5.js says: You can not have less than one instance');
count = 1;
}
this._renderer.endShape(mode, count);
};
/**
* Sets the normal vector for vertices in a custom 3D shape.
*
* 3D shapes created with beginShape() and
* endShape() are made by connecting sets of
* points called vertices. Each vertex added with
* vertex() has a normal vector that points away
* from it. The normal vector controls how light reflects off the shape.
*
* `normal()` can be called two ways with different parameters to define the
* normal vector's components.
*
* The first way to call `normal()` has three parameters, `x`, `y`, and `z`.
* If `Number`s are passed, as in `normal(1, 2, 3)`, they set the x-, y-, and
* z-components of the normal vector.
*
* The second way to call `normal()` has one parameter, `vector`. If a
* p5.Vector object is passed, as in
* `normal(myVector)`, its components will be used to set the normal vector.
*
* `normal()` changes the normal vector of vertices added to a custom shape
* with vertex(). `normal()` must be called between
* the beginShape() and
* endShape() functions, just like
* vertex(). The normal vector set by calling
* `normal()` will affect all following vertices until `normal()` is called
* again:
*
* ```js
* beginShape();
*
* // Set the vertex normal.
* normal(-0.4, -0.4, 0.8);
*
* // Add a vertex.
* vertex(-30, -30, 0);
*
* // Set the vertex normal.
* normal(0, 0, 1);
*
* // Add vertices.
* vertex(30, -30, 0);
* vertex(30, 30, 0);
*
* // Set the vertex normal.
* normal(0.4, -0.4, 0.8);
*
* // Add a vertex.
* vertex(-30, 30, 0);
*
* endShape();
* ```
*
* @method normal
* @param {p5.Vector} vector vertex normal as a p5.Vector object.
* @chainable
*
* @example
* // Click the and drag the mouse to view the scene from a different angle.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe(
* 'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
* );
* }
*
* function draw() {
* background(0);
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Style the shape.
* normalMaterial();
* noStroke();
*
* // Draw the shape.
* beginShape();
* vertex(-30, -30, 0);
* vertex(30, -30, 0);
* vertex(30, 30, 0);
* vertex(-30, 30, 0);
* endShape();
* }
*
* @example
* // Click the and drag the mouse to view the scene from a different angle.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe(
* 'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
* );
* }
*
* function draw() {
* background(0);
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Style the shape.
* normalMaterial();
* noStroke();
*
* // Draw the shape.
* // Use normal() to set vertex normals.
* beginShape();
* normal(-0.4, -0.4, 0.8);
* vertex(-30, -30, 0);
*
* normal(0, 0, 1);
* vertex(30, -30, 0);
* vertex(30, 30, 0);
*
* normal(0.4, -0.4, 0.8);
* vertex(-30, 30, 0);
* endShape();
* }
*
* @example
* // Click the and drag the mouse to view the scene from a different angle.
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe(
* 'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
* );
* }
*
* function draw() {
* background(0);
*
* // Enable orbiting with the mouse.
* orbitControl();
*
* // Style the shape.
* normalMaterial();
* noStroke();
*
* // Create p5.Vector objects.
* let n1 = createVector(-0.4, -0.4, 0.8);
* let n2 = createVector(0, 0, 1);
* let n3 = createVector(0.4, -0.4, 0.8);
*
* // Draw the shape.
* // Use normal() to set vertex normals.
* beginShape();
* normal(n1);
* vertex(-30, -30, 0);
*
* normal(n2);
* vertex(30, -30, 0);
* vertex(30, 30, 0);
*
* normal(n3);
* vertex(-30, 30, 0);
* endShape();
* }
*/
/**
* @method normal
* @param {Number} x x-component of the vertex normal.
* @param {Number} y y-component of the vertex normal.
* @param {Number} z z-component of the vertex normal.
* @chainable
*/
fn.normal = function(x, y, z) {
this._assert3d('normal');
// p5._validateParameters('normal', arguments);
this._renderer.normal(...arguments);
return this;
};
/**
* Sets the shader's vertex property or attribute variables.
*
* A vertex property, or vertex attribute, is a variable belonging to a vertex in a shader. p5.js provides some
* default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are
* set using vertex(), normal()
* and fill() respectively. Custom properties can also
* be defined within beginShape() and
* endShape().
*
* The first parameter, `propertyName`, is a string with the property's name.
* This is the same variable name which should be declared in the shader, such as
* `in vec3 aProperty`, similar to .`setUniform()`.
*
* The second parameter, `data`, is the value assigned to the shader variable. This
* value will be applied to subsequent vertices created with
* vertex(). It can be a Number or an array of numbers,
* and in the shader program the type can be declared according to the WebGL
* specification. Common types include `float`, `vec2`, `vec3`, `vec4` or matrices.
*
* See also the vertexProperty() method on
* Geometry objects.
*
* @method vertexProperty
* @for p5
* @param {String} attributeName the name of the vertex attribute.
* @param {Number|Number[]} data the data tied to the vertex attribute.
*
* @example
* const vertSrc = `#version 300 es
* precision mediump float;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* in vec3 aPosition;
* in vec2 aOffset;
*
* void main(){
* vec4 positionVec4 = vec4(aPosition.xyz, 1.0);
* positionVec4.xy += aOffset;
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* const fragSrc = `#version 300 es
* precision mediump float;
* out vec4 outColor;
* void main(){
* outColor = vec4(0.0, 1.0, 1.0, 1.0);
* }
* `;
*
* function setup(){
* createCanvas(100, 100, WEBGL);
*
* // Create and use the custom shader.
* const myShader = createShader(vertSrc, fragSrc);
* shader(myShader);
*
* describe('A wobbly, cyan circle on a gray background.');
* }
*
* function draw(){
* // Set the styles
* background(125);
* noStroke();
*
* // Draw the circle.
* beginShape();
* for (let i = 0; i < 30; i++){
* const x = 40 * cos(i/30 * TWO_PI);
* const y = 40 * sin(i/30 * TWO_PI);
*
* // Apply some noise to the coordinates.
* const xOff = 10 * noise(x + millis()/1000) - 5;
* const yOff = 10 * noise(y + millis()/1000) - 5;
*
* // Apply these noise values to the following vertex.
* vertexProperty('aOffset', [xOff, yOff]);
* vertex(x, y);
* }
* endShape(CLOSE);
* }
*
* @example
* let myShader;
* const cols = 10;
* const rows = 10;
* const cellSize = 6;
*
* const vertSrc = `#version 300 es
* precision mediump float;
* uniform mat4 uProjectionMatrix;
* uniform mat4 uModelViewMatrix;
*
* in vec3 aPosition;
* in vec3 aNormal;
* in vec3 aVertexColor;
* in float aDistance;
*
* out vec3 vVertexColor;
*
* void main(){
* vec4 positionVec4 = vec4(aPosition, 1.0);
* positionVec4.xyz += aDistance * aNormal * 2.0;;
* vVertexColor = aVertexColor;
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* const fragSrc = `#version 300 es
* precision mediump float;
*
* in vec3 vVertexColor;
* out vec4 outColor;
*
* void main(){
* outColor = vec4(vVertexColor, 1.0);
* }
* `;
*
* function setup(){
* createCanvas(100, 100, WEBGL);
*
* // Create and apply the custom shader.
* myShader = createShader(vertSrc, fragSrc);
* shader(myShader);
* noStroke();
* describe('A blue grid, which moves away from the mouse position, on a gray background.');
* }
*
* function draw(){
* background(200);
*
* // Draw the grid in the middle of the screen.
* translate(-cols*cellSize/2, -rows*cellSize/2);
* beginShape(QUADS);
* for (let i = 0; i < cols; i++) {
* for (let j = 0; j < rows; j++) {
*
* // Calculate the cell position.
* let x = i * cellSize;
* let y = j * cellSize;
*
* fill(j/rows*255, j/cols*255, 255);
*
* // Calculate the distance from the corner of each cell to the mouse.
* let distance = dist(x, y, mouseX, mouseY);
*
* // Send the distance to the shader.
* vertexProperty('aDistance', min(distance, 100));
*
* vertex(x, y);
* vertex(x + cellSize, y);
* vertex(x + cellSize, y + cellSize);
* vertex(x, y + cellSize);
* }
* }
* endShape();
* }
*/
fn.vertexProperty = function(attributeName, data){
// this._assert3d('vertexProperty');
// p5._validateParameters('vertexProperty', arguments);
this._renderer.vertexProperty(attributeName, data);
};
}
if(typeof p5 !== 'undefined'){
vertex(p5, p5.prototype);
}
/**
* @module Color
* @submodule Setting
* @for p5
* @requires core
* @requires constants
*/
function setting(p5, fn){
/**
* Starts defining a shape that will mask any shapes drawn afterward.
*
* Any shapes drawn between `beginClip()` and
* endClip() will add to the mask shape. The mask
* will apply to anything drawn after endClip().
*
* The parameter, `options`, is optional. If an object with an `invert`
* property is passed, as in `beginClip({ invert: true })`, it will be used to
* set the masking mode. `{ invert: true }` inverts the mask, creating holes
* in shapes that are masked. `invert` is `false` by default.
*
* Masks can be contained between the
* push() and pop() functions.
* Doing so allows unmasked shapes to be drawn after masked shapes.
*
* Masks can also be defined in a callback function that's passed to
* clip().
*
* @method beginClip
* @param {Object} [options] an object containing clip settings.
* @param {Boolean} [options.invert=false] Whether or not to invert the mask.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a mask.
* beginClip();
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* endClip();
*
* // Draw a backing shape.
* square(5, 5, 45);
*
* describe('A white triangle and circle on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create an inverted mask.
* beginClip({ invert: true });
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* endClip();
*
* // Draw a backing shape.
* square(5, 5, 45);
*
* describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* noStroke();
*
* // Draw a masked shape.
* push();
* // Create a mask.
* beginClip();
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* endClip();
*
* // Draw a backing shape.
* square(5, 5, 45);
* pop();
*
* // Translate the origin to the center.
* translate(50, 50);
*
* // Draw an inverted masked shape.
* push();
* // Create an inverted mask.
* beginClip({ invert: true });
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* endClip();
*
* // Draw a backing shape.
* square(5, 5, 45);
* pop();
*
* describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A silhouette of a rotating torus colored fuchsia.');
* }
*
* function draw() {
* background(200);
*
* // Create a mask.
* beginClip();
* push();
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
* scale(0.5);
* torus(30, 15);
* pop();
* endClip();
*
* // Draw a backing shape.
* noStroke();
* fill('fuchsia');
* plane(100);
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.');
* }
*
* function draw() {
* background(200);
*
* // Create a mask.
* beginClip();
* push();
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
* scale(0.5);
* torus(30, 15);
* pop();
* endClip();
*
* // Draw a backing shape.
* noStroke();
* beginShape(QUAD_STRIP);
* fill(0, 255, 255);
* vertex(-width / 2, -height / 2);
* vertex(width / 2, -height / 2);
* fill(100, 0, 100);
* vertex(-width / 2, height / 2);
* vertex(width / 2, height / 2);
* endShape();
* }
*/
fn.beginClip = function(options = {}) {
this._renderer.beginClip(options);
};
/**
* Ends defining a mask that was started with
* beginClip().
*
* @method endClip
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a mask.
* beginClip();
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* endClip();
*
* // Draw a backing shape.
* square(5, 5, 45);
*
* describe('A white triangle and circle on a gray background.');
* }
*/
fn.endClip = function() {
this._renderer.endClip();
};
/**
* Defines a shape that will mask any shapes drawn afterward.
*
* The first parameter, `callback`, is a function that defines the mask.
* Any shapes drawn in `callback` will add to the mask shape. The mask
* will apply to anything drawn after `clip()` is called.
*
* The second parameter, `options`, is optional. If an object with an `invert`
* property is passed, as in `beginClip({ invert: true })`, it will be used to
* set the masking mode. `{ invert: true }` inverts the mask, creating holes
* in shapes that are masked. `invert` is `false` by default.
*
* Masks can be contained between the
* push() and pop() functions.
* Doing so allows unmasked shapes to be drawn after masked shapes.
*
* Masks can also be defined with beginClip()
* and endClip().
*
* @method clip
* @param {Function} callback a function that draws the mask shape.
* @param {Object} [options] an object containing clip settings.
* @param {Boolean} [options.invert=false] Whether or not to invert the mask.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a mask.
* clip(mask);
*
* // Draw a backing shape.
* square(5, 5, 45);
*
* describe('A white triangle and circle on a gray background.');
* }
*
* // Declare a function that defines the mask.
* function mask() {
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create an inverted mask.
* clip(mask, { invert: true });
*
* // Draw a backing shape.
* square(5, 5, 45);
*
* describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.');
* }
*
* // Declare a function that defines the mask.
* function mask() {
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* noStroke();
*
* // Draw a masked shape.
* push();
* // Create a mask.
* clip(mask);
*
* // Draw a backing shape.
* square(5, 5, 45);
* pop();
*
* // Translate the origin to the center.
* translate(50, 50);
*
* // Draw an inverted masked shape.
* push();
* // Create an inverted mask.
* clip(mask, { invert: true });
*
* // Draw a backing shape.
* square(5, 5, 45);
* pop();
*
* describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.');
* }
*
* // Declare a function that defines the mask.
* function mask() {
* triangle(15, 37, 30, 13, 43, 37);
* circle(45, 45, 7);
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A silhouette of a rotating torus colored fuchsia.');
* }
*
* function draw() {
* background(200);
*
* // Create a mask.
* clip(mask);
*
* // Draw a backing shape.
* noStroke();
* fill('fuchsia');
* plane(100);
* }
*
* // Declare a function that defines the mask.
* function mask() {
* push();
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
* scale(0.5);
* torus(30, 15);
* pop();
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.');
* }
*
* function draw() {
* background(200);
*
* // Create a mask.
* clip(mask);
*
* // Draw a backing shape.
* noStroke();
* beginShape(QUAD_STRIP);
* fill(0, 255, 255);
* vertex(-width / 2, -height / 2);
* vertex(width / 2, -height / 2);
* fill(100, 0, 100);
* vertex(-width / 2, height / 2);
* vertex(width / 2, height / 2);
* endShape();
* }
*
* // Declare a function that defines the mask.
* function mask() {
* push();
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
* scale(0.5);
* torus(30, 15);
* pop();
* }
*/
fn.clip = function(callback, options) {
this._renderer.beginClip(options);
callback();
this._renderer.endClip(options);
};
/**
* Sets the color used for the background of the canvas.
*
* By default, the background is transparent. `background()` is typically used
* within draw() to clear the display window at the
* beginning of each frame. It can also be used inside
* setup() to set the background on the first frame
* of animation.
*
* The version of `background()` with one parameter interprets the value one
* of four ways. If the parameter is a `Number`, it's interpreted as a grayscale
* value. If the parameter is a `String`, it's interpreted as a CSS color string.
* RGB, RGBA, HSL, HSLA, hex, and named color strings are supported. If the
* parameter is a p5.Color object, it will be used as
* the background color. If the parameter is a
* p5.Image object, it will be used as the background
* image.
*
* The version of `background()` with two parameters interprets the first one
* as a grayscale value. The second parameter sets the alpha (transparency)
* value.
*
* The version of `background()` with three parameters interprets them as RGB,
* HSB, or HSL colors, depending on the current
* colorMode(). By default, colors are specified
* in RGB values. Calling `background(255, 204, 0)` sets the background a bright
* yellow color.
*
* The version of `background()` with four parameters interprets them as RGBA,
* HSBA, or HSLA colors, depending on the current
* colorMode(). The last parameter sets the alpha
* (transparency) value.
*
* @method background
* @param {p5.Color} color any value created by the color() function
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // A grayscale value.
* background(51);
*
* describe('A canvas with a dark charcoal gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // A grayscale value and an alpha value.
* background(51, 0.4);
* describe('A canvas with a transparent gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // R, G & B values.
* background(255, 204, 0);
*
* describe('A canvas with a yellow background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // R, G, B, and Alpha values.
* background(255, 0, 0, 128);
*
* describe('A canvas with a semi-transparent red background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Use HSB color.
* colorMode(HSB);
*
* // H, S & B values.
* background(255, 204, 100);
*
* describe('A canvas with a royal blue background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // A CSS named color.
* background('red');
*
* describe('A canvas with a red background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Three-digit hex RGB notation.
* background('#fae');
*
* describe('A canvas with a pink background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Six-digit hex RGB notation.
* background('#222222');
*
* describe('A canvas with a black background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Integer RGB notation.
* background('rgb(0, 255, 0)');
*
* describe('A canvas with a bright green background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Integer RGBA notation.
* background('rgba(0, 255, 0, 0.25)');
*
* describe('A canvas with a transparent green background.');
* }
* <
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Percentage RGB notation.
* background('rgb(100%, 0%, 10%)');
*
* describe('A canvas with a red background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Percentage RGBA notation.
* background('rgba(100%, 0%, 100%, 0.5)');
*
* describe('A canvas with a transparent purple background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // A p5.Color object.
* let c = color(0, 0, 255);
* background(c);
*
* describe('A canvas with a blue background.');
* }
*/
/**
* @method background
* @param {String} colorstring color string, possible formats include: integer
* rgb() or rgba(), percentage rgb() or rgba(),
* 3-digit hex, 6-digit hex.
* @param {Number} [a] opacity of the background relative to current
* color range (default is 0-255).
* @chainable
*/
/**
* @method background
* @param {Number} gray specifies a value between white and black.
* @param {Number} [a]
* @chainable
*/
/**
* @method background
* @param {Number} v1 red value if color mode is RGB, or hue value if color mode is HSB.
* @param {Number} v2 green value if color mode is RGB, or saturation value if color mode is HSB.
* @param {Number} v3 blue value if color mode is RGB, or brightness value if color mode is HSB.
* @param {Number} [a]
* @chainable
*/
/**
* @method background
* @param {Number[]} values an array containing the red, green, blue
* and alpha components of the color.
* @chainable
*/
/**
* @method background
* @param {p5.Image} image image created with loadImage()
* or createImage(),
* to set as background.
* (must be same size as the sketch window).
* @param {Number} [a]
* @chainable
*/
fn.background = function(...args) {
this._renderer.background(...args);
return this;
};
/**
* Clears the pixels on the canvas.
*
* `clear()` makes every pixel 100% transparent. Calling `clear()` doesn't
* clear objects created by `createX()` functions such as
* createGraphics(),
* createVideo(), and
* createImg(). These objects will remain
* unchanged after calling `clear()` and can be redrawn.
*
* In WebGL mode, this function can clear the screen to a specific color. It
* interprets four numeric parameters as normalized RGBA color values. It also
* clears the depth buffer. If you are not using the WebGL renderer, these
* parameters will have no effect.
*
* @method clear
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* describe('A gray square. White circles are drawn as the user moves the mouse. The circles disappear when the user presses the mouse.');
* }
*
* function draw() {
* circle(mouseX, mouseY, 20);
* }
*
* function mousePressed() {
* clear();
* background(200);
* }
*
* @example
* let pg;
*
* function setup() {
* createCanvas(100, 100);
* background(200);
*
* pg = createGraphics(60, 60);
* pg.background(200);
* pg.noStroke();
* pg.circle(pg.width / 2, pg.height / 2, 15);
* image(pg, 20, 20);
*
* describe('A white circle drawn on a gray square. The square gets smaller when the mouse is pressed.');
* }
*
* function mousePressed() {
* clear();
* image(pg, 20, 20);
* }
*
* @param {Number} [r] normalized red value.
* @param {Number} [g] normalized green value.
* @param {Number} [b] normalized blue value.
* @param {Number} [a] normalized alpha value.
*/
fn.clear = function(...args) {
const _r = args[0] || 0;
const _g = args[1] || 0;
const _b = args[2] || 0;
const _a = args[3] || 0;
this._renderer.clear(_r, _g, _b, _a);
return this;
};
/**
* Changes the way color values are interpreted.
*
* By default, the `Number` parameters for fill(),
* stroke(),
* background(), and
* color() are defined by values between 0 and 255
* using the RGB color model. This is equivalent to calling
* `colorMode(RGB, 255)`. Pure red is `color(255, 0, 0)` in this model.
*
* Calling `colorMode(RGB, 100)` sets colors to use RGB color values
* between 0 and 100. Pure red is `color(100, 0, 0)` in this model.
*
* Calling `colorMode(HSB)` or `colorMode(HSL)` changes to HSB or HSL systems instead of RGB.
* Pure red is `color(0, 100, 100)` in HSB and `color(0, 100, 50)` in HSL.
*
* Some additional color modes that p5.js supports are:
*
* `RGBHDR` - High Dynamic Range RGB defined within the Display P3 color space.
* Colors are expressed with an extended dynamic range. To render these colors
* accurately, you must use the HDR canvas.
*
* `HWB` - Hue, Whiteness, Blackness.
* Similar to HSB and HSL, this mode uses a hue angle.
* Instead of saturation and lightness, HWB defines colors based on the percentage
* of whiteness and blackness. This is the color model used by Chrome's GUI color picker.
* Pure red in HWB is represented as `color(0, 0, 0)` (i.e., hue 0 with 0% whiteness and 0% blackness).
*
*
*
* `LAB` - Also known as CIE Lab, this color mode defines colors with Lightness, Alpha, and Beta.
* It is widely used in professional color measurement contexts due to its perceptual uniformity.
*
* `LCH` - A more intuitive representation of the CIE Lab color space using Lightness, Chroma, and Hue.
* This mode separates the color's chromatic intensity (chroma) from its lightness,
* simplifying color selection and manipulation.
*
* `OKLAB` - A variant of the CIE Lab color space that corrects for non-uniformities inherent in LAB.
* The adjustment provides a more perceptually accurate and uniform representation,
* which is particularly beneficial for smooth color transitions.
*
* `OKLCH` - An easier-to-use representation of OKLAB, expressing colors in terms of Lightness, Chroma, and Hue.
* This mode retains the perceptual benefits of OKLAB while offering a more intuitive format for color manipulation.
*
* p5.Color objects remember the mode that they were
* created in. Changing modes doesn't affect their appearance.
*
* `Single-value (Grayscale) Colors`:
* When a color is specified with only one parameter (e.g., `color(g)`), p5.js will interpret it
* as a grayscale color. However, how that single parameter translates into a grayscale value
* depends on the color mode:
*
* - `RGB, HSB, and HSL`: In RGB, the single value is interpreted using the “blue” maximum
* (i.e., the single parameter is mapped to the blue channel's max).
* In HSB and HSL, the single value is mapped to Brightness and Lightness max respectively with hue=0 .
* and saturation=0.
*
* - `LAB, LCH, OKLAB, and OKLCH`: The single value is taken to be the `lightness (L)` component,
* with the specified max range for that channel.
*
* - `HWB`: Grayscale relies on both the `whiteness (W)` and `blackness (B)` channels. Since
* a single value cannot directly account for two distinct channels, the library uses an
* average of their max values to interpret the single grayscale parameter. For instance,
* if W has a max of 50 and B has a max of 100, then the single grayscale parameter
* is mapped using (50 + 100) / 2 = 75 as its effective maximum. More complex or negative
* ranges are currently not handled, so results in those cases may be ambiguous.
*
* @method colorMode
* @param {RGB|HSB|HSL|RGBHDR|HWB|LAB|LCH|OKLAB|OKLCH} mode either RGB, HSB, HSL,
* or one of the extended modes described above.
* @param {Number} [max] range for all values.
* @return {RGB|HSB|HSL|RGBHDR|HWB|LAB|LCH|OKLAB|OKLCH} The current color mode.
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Fill with pure red.
* fill(255, 0, 0);
*
* circle(50, 50, 25);
*
* describe('A gray square with a red circle at its center.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use RGB color with values in the range 0-100.
* colorMode(RGB, 100);
*
* // Fill with pure red.
* fill(100, 0, 0);
*
* circle(50, 50, 25);
*
* describe('A gray square with a red circle at its center.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use HSB color.
* colorMode(HSB);
*
* // Fill with pure red.
* fill(0, 100, 100);
*
* circle(50, 50, 25);
*
* describe('A gray square with a red circle at its center.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use HSL color.
* colorMode(HSL);
*
* // Fill with pure red.
* fill(0, 100, 50);
*
* circle(50, 50, 25);
*
* describe('A gray square with a red circle at its center.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Draw a neutral gray background using the default color mode.
* background(200);
*
* // Switch to HWB color mode.
* // (Assuming p5.js supports HWB with a range of:
* // hue: 0–360, whiteness: 0–100, blackness: 0–100.)
* colorMode(HWB);
*
* // Set fill to pure red in HWB.
* // Pure red in HWB is: hue = 0°, whiteness = 0%, blackness = 0%.
* fill(0, 0, 0);
*
* // Draw a circle at the center.
* circle(50, 50, 25);
*
* describe('A gray square with a red circle at its center, drawn using HWB color mode.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Draw a neutral gray background using the default color mode.
* background(200);
*
* // Switch to LAB color mode.
* // In this mode, L typically ranges from 0 to 100 while a and b span roughly -128 to 127.
* colorMode(LAB);
*
* // Set fill to pure red in LAB.
* // The sRGB red (255, 0, 0) converts approximately to LAB as:
* // L = 53, a = 80, b = 67.
* fill(53, 80, 67);
*
* // Draw a circle at the center.
* circle(50, 50, 25);
*
* describe('A gray square with a red circle at its center, drawn using LAB color mode.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Draw a neutral gray background.
* background(200);
*
* // Switch to LCH color mode.
* // In LCH, colors are defined by Lightness, Chroma, and Hue (in degrees).
* colorMode(LCH);
*
* // Set fill to an approximation of pure red in LCH:
* // Lightness ≈ 53, Chroma ≈ 104, Hue ≈ 40°.
* fill(53, 104, 40);
*
* // Draw a circle at the center.
* circle(50, 50, 25);
*
* describe('A gray square with a red circle at its center, drawn using LCH color mode.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Use RGB color with values in the range 0-100.
* colorMode(RGB, 100);
*
* for (let x = 0; x < 100; x += 1) {
* for (let y = 0; y < 100; y += 1) {
* stroke(x, y, 0);
* point(x, y);
* }
* }
*
* describe(
* 'A diagonal green to red gradient from bottom-left to top-right with shading transitioning to black at top-left corner.'
* );
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Use HSB color with values in the range 0-100.
* colorMode(HSB, 100);
*
* for (let x = 0; x < 100; x += 1) {
* for (let y = 0; y < 100; y += 1) {
* stroke(x, y, 100);
* point(x, y);
* }
* }
*
* describe('A rainbow gradient from left-to-right. Brightness transitions to white at the top.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* // Create a p5.Color object.
* let myColor = color(180, 175, 230);
* background(myColor);
*
* // Use RGB color with values in the range 0-1.
* colorMode(RGB, 1);
*
* // Get the red, green, and blue color components.
* let redValue = red(myColor);
* let greenValue = green(myColor);
* let blueValue = blue(myColor);
*
* // Round the color components for display.
* redValue = round(redValue, 2);
* greenValue = round(greenValue, 2);
* blueValue = round(blueValue, 2);
*
* // Display the color components.
* text(`Red: ${redValue}`, 10, 10, 80, 80);
* text(`Green: ${greenValue}`, 10, 40, 80, 80);
* text(`Blue: ${blueValue}`, 10, 70, 80, 80);
*
* describe('A purple canvas with the red, green, and blue decimal values of the color written on it.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(255);
*
* // Use RGB color with alpha values in the range 0-1.
* colorMode(RGB, 255, 255, 255, 1);
*
* noFill();
* strokeWeight(4);
* stroke(255, 0, 10, 0.3);
* circle(40, 40, 50);
* circle(50, 60, 50);
*
* describe('Two overlapping translucent pink circle outlines.');
* }
*
* @example
* let hslGraphic, lchGraphic, oklchGraphic;
*
* function setup() {
* createCanvas(600, 200);
* noLoop();
*
* // Create three graphics objects for HSL, LCH, and OKLCH color modes
* hslGraphic = createGraphics(200, 200);
* lchGraphic = createGraphics(200, 200);
* oklchGraphic = createGraphics(200, 200);
*
* // Draw HSL color wheel
* colorMode(HSL);
* hslGraphic.translate(100, 100);
* for (let i = 0; i < 1000; i++) {
* hslGraphic.stroke(360 / 1000 * i, 70, 50);
* hslGraphic.line(0, 0, hslGraphic.width / 2, 0);
* hslGraphic.rotate(TAU / 1000);
* }
*
* // Draw LCH color wheel
* colorMode(LCH);
* lchGraphic.translate(100, 100);
* for (let i = 0; i < 1000; i++) {
* lchGraphic.stroke(54, 106, 360 / 1000 * i);
* lchGraphic.line(0, 0, lchGraphic.width / 2, 0);
* lchGraphic.rotate(TAU / 1000);
* }
*
* // Draw OKLCH color wheel
* colorMode(OKLCH);
* oklchGraphic.translate(100, 100);
* for (let i = 0; i < 1000; i++) {
* oklchGraphic.stroke(54, 106, 360 / 1000 * i);
* oklchGraphic.line(0, 0, oklchGraphic.width / 2, 0);
* oklchGraphic.rotate(TAU / 1000);
* }
* }
*
* function draw() {
* // Set the styles
* colorMode(RGB);
* background(220);
*
* // Display the color wheels
* image(hslGraphic, 0, 0);
* image(lchGraphic, 200, 0);
* image(oklchGraphic, 400, 0);
* }
*
* @example
* // Example: Single-value (Grayscale) colors in different color modes.
* // The rectangle is filled with one parameter, but its final color depends
* // on how that parameter is interpreted by the current color mode.
*
* function setup() {
* createCanvas(100, 100);
* noStroke();
* noLoop();
* }
*
* function draw() {
* // Set color mode to RGB with range 0-255
* colorMode(RGB, 255);
*
* // Fill with single grayscale value
* fill(128);
* rect(0, 0, 100, 100);
*
* // Add text label
* fill(0); // Switch to black text for clarity
* textSize(14);
* text("RGB (128)", 10, 20);
* }
*/
/**
* @method colorMode
* @param {RGB|HSB|HSL|RGBHDR|HWB|LAB|LCH|OKLAB|OKLCH} mode
* @param {Number} max1 range for the red or hue depending on the
* current color mode.
* @param {Number} max2 range for the green or saturation depending
* on the current color mode.
* @param {Number} max3 range for the blue or brightness/lightness
* depending on the current color mode.
* @param {Number} [maxA] range for the alpha.
*
* @return {RGB|HSB|HSL|RGBHDR|HWB|LAB|LCH|OKLAB|OKLCH} The current color mode.
*/
/**
* @method colorMode
* @return {RGB|HSB|HSL|RGBHDR|HWB|LAB|LCH|OKLAB|OKLCH} The current color mode.
*/
fn.colorMode = function(mode, max1, max2, max3, maxA) {
// p5._validateParameters('colorMode', arguments);
if (
[
RGB,
RGBHDR,
HSB,
HSL,
HWB,
LAB,
LCH,
OKLAB,
OKLCH
].includes(mode)
) {
// Set color mode.
this._renderer.states.setValue('colorMode', mode);
// Set color maxes.
this._renderer.states.setValue('colorMaxes', this._renderer.states.colorMaxes.clone());
const maxes = this._renderer.states.colorMaxes[mode];
if (arguments.length === 2) {
maxes[0] = max1; // Red
maxes[1] = max1; // Green
maxes[2] = max1; // Blue
maxes[3] = max1; // Alpha
} else if (arguments.length === 4) {
maxes[0] = max1; // Red
maxes[1] = max2; // Green
maxes[2] = max3; // Blue
} else if (arguments.length === 5) {
maxes[0] = max1; // Red
maxes[1] = max2; // Green
maxes[2] = max3; // Blue
maxes[3] = maxA; // Alpha
}
}
return this._renderer.states.colorMode;
};
/**
* Sets the color used to fill shapes.
*
* Calling `fill(255, 165, 0)` or `fill('orange')` means all shapes drawn
* after the fill command will be filled with the color orange.
*
* The version of `fill()` with one parameter interprets the value one of
* three ways. If the parameter is a `Number`, it's interpreted as a grayscale
* value. If the parameter is a `String`, it's interpreted as a CSS color
* string. A p5.Color object can also be provided to
* set the fill color.
*
* The version of `fill()` with three parameters interprets them as RGB, HSB,
* or HSL colors, depending on the current
* colorMode(). The default color space is RGB,
* with each value in the range from 0 to 255.
*
* The version of `fill()` with four parameters interprets them as `RGBA`, `HSBA`,
* or `HSLA` colors, depending on the current colorMode(). The last parameter
* sets the alpha (transparency) value.
*
* @method fill
* @param {Number} v1 red value if color mode is RGB or hue value if color mode is HSB.
* @param {Number} v2 green value if color mode is RGB or saturation value if color mode is HSB.
* @param {Number} v3 blue value if color mode is RGB or brightness value if color mode is HSB.
* @param {Number} [alpha] alpha value, controls transparency (0 - transparent, 255 - opaque).
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // A grayscale value.
* fill(51);
* square(20, 20, 60);
*
* describe('A dark charcoal gray square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // R, G & B values.
* fill(255, 204, 0);
* square(20, 20, 60);
*
* describe('A yellow square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // R, G, B, and Alpha values.
* fill(255, 0, 0, 128);
* square(20, 20, 60);
*
* describe('A semi-transparent red square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(100);
*
* // Use HSB color.
* colorMode(HSB);
*
* // H, S & B values.
* fill(255, 204, 100);
* square(20, 20, 60);
*
* describe('A royal blue square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // A CSS named color.
* fill('red');
* square(20, 20, 60);
*
* describe('A red square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Three-digit hex RGB notation.
* fill('#fae');
* square(20, 20, 60);
*
* describe('A pink square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Six-digit hex RGB notation.
* fill('#A251FA');
* square(20, 20, 60);
*
* describe('A purple square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Integer RGB notation.
* fill('rgb(0, 255, 0)');
* square(20, 20, 60);
*
* describe('A bright green square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Integer RGBA notation.
* fill('rgba(0, 255, 0, 0.25)');
* square(20, 20, 60);
*
* describe('A soft green rectange with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Percentage RGB notation.
* fill('rgb(100%, 0%, 10%)');
* square(20, 20, 60);
*
* describe('A red square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Percentage RGBA notation.
* fill('rgba(100%, 0%, 100%, 0.5)');
* square(20, 20, 60);
*
* describe('A dark fuchsia square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // A p5.Color object.
* let c = color(0, 0, 255);
* fill(c);
* square(20, 20, 60);
*
* describe('A blue square with a black outline.');
* }
*/
/**
* @method fill
* @param {String} value a color string.
* @chainable
*/
/**
* @method fill
* @param {Number} gray a grayscale value.
* @param {Number} [alpha]
* @chainable
*/
/**
* @method fill
* @param {Number[]} values an array containing the red, green, blue &
* and alpha components of the color.
* @chainable
*/
/**
* @method fill
* @param {p5.Color} color the fill color.
* @chainable
*/
fn.fill = function(...args) {
this._renderer.fill(...args);
return this;
};
/**
* Disables setting the fill color for shapes.
*
* Calling `noFill()` is the same as making the fill completely transparent,
* as in `fill(0, 0)`. If both noStroke() and
* `noFill()` are called, nothing will be drawn to the screen.
*
* @method noFill
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Draw the top square.
* square(32, 10, 35);
*
* // Draw the bottom square.
* noFill();
* square(32, 55, 35);
*
* describe('A white square on above an empty square. Both squares have black outlines.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A purple cube wireframe spinning on a black canvas.');
* }
*
* function draw() {
* background(0);
*
* // Style the box.
* noFill();
* stroke(100, 100, 240);
*
* // Rotate the coordinates.
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
*
* // Draw the box.
* box(45);
* }
*/
fn.noFill = function() {
this._renderer.noFill();
return this;
};
/**
* Disables drawing points, lines, and the outlines of shapes.
*
* Calling `noStroke()` is the same as making the stroke completely transparent,
* as in `stroke(0, 0)`. If both `noStroke()` and
* noFill() are called, nothing will be drawn to the
* screen.
*
* @method noStroke
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* noStroke();
* square(20, 20, 60);
*
* describe('A white square with no outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A pink cube with no edge outlines spinning on a black canvas.');
* }
*
* function draw() {
* background(0);
*
* // Style the box.
* noStroke();
* fill(240, 150, 150);
*
* // Rotate the coordinates.
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
*
* // Draw the box.
* box(45);
* }
*/
fn.noStroke = function() {
this._renderer.states.setValue('strokeColor', null);
return this;
};
/**
* Sets the color used to draw points, lines, and the outlines of shapes.
*
* Calling `stroke(255, 165, 0)` or `stroke('orange')` means all shapes drawn
* after calling `stroke()` will be outlined with the color orange. The way
* these parameters are interpreted may be changed with the
* colorMode() function.
*
* The version of `stroke()` with one parameter interprets the value one of
* three ways. If the parameter is a `Number`, it's interpreted as a grayscale
* value. If the parameter is a `String`, it's interpreted as a CSS color
* string. A p5.Color object can also be provided to
* set the stroke color.
*
* The version of `stroke()` with two parameters interprets the first one as a
* grayscale value. The second parameter sets the alpha (transparency) value.
*
* The version of `stroke()` with three parameters interprets them as RGB, HSB,
* or HSL colors, depending on the current `colorMode()`.
*
* The version of `stroke()` with four parameters interprets them as RGBA, HSBA,
* or HSLA colors, depending on the current `colorMode()`. The last parameter
* sets the alpha (transparency) value.
*
* @method stroke
* @param {Number} v1 red value if color mode is RGB or hue value if color mode is HSB.
* @param {Number} v2 green value if color mode is RGB or saturation value if color mode is HSB.
* @param {Number} v3 blue value if color mode is RGB or brightness value if color mode is HSB.
* @param {Number} [alpha] alpha value, controls transparency (0 - transparent, 255 - opaque).
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // A grayscale value.
* strokeWeight(4);
* stroke(51);
* square(20, 20, 60);
*
* describe('A white square with a dark charcoal gray outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // R, G & B values.
* stroke(255, 204, 0);
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a yellow outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use HSB color.
* colorMode(HSB);
*
* // H, S & B values.
* strokeWeight(4);
* stroke(255, 204, 100);
* square(20, 20, 60);
*
* describe('A white square with a royal blue outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // A CSS named color.
* stroke('red');
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a red outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Three-digit hex RGB notation.
* stroke('#fae');
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a pink outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Six-digit hex RGB notation.
* stroke('#222222');
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a black outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Integer RGB notation.
* stroke('rgb(0, 255, 0)');
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a bright green outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Integer RGBA notation.
* stroke('rgba(0, 255, 0, 0.25)');
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a soft green outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Percentage RGB notation.
* stroke('rgb(100%, 0%, 10%)');
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a red outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Percentage RGBA notation.
* stroke('rgba(100%, 0%, 100%, 0.5)');
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a dark fuchsia outline.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // A p5.Color object.
* stroke(color(0, 0, 255));
* strokeWeight(4);
* square(20, 20, 60);
*
* describe('A white square with a blue outline.');
* }
*/
/**
* @method stroke
* @param {String} value a color string.
* @chainable
*/
/**
* @method stroke
* @param {Number} gray a grayscale value.
* @param {Number} [alpha]
* @chainable
*/
/**
* @method stroke
* @param {Number[]} values an array containing the red, green, blue,
* and alpha components of the color.
* @chainable
*/
/**
* @method stroke
* @param {p5.Color} color the stroke color.
* @chainable
*/
fn.stroke = function(...args) {
this._renderer.stroke(...args);
return this;
};
/**
* Starts using shapes to erase parts of the canvas.
*
* All drawing that follows `erase()` will subtract from the canvas, revealing
* the web page underneath. The erased areas will become transparent, allowing
* the content behind the canvas to show through. The
* fill(), stroke(), and
* blendMode() have no effect once `erase()` is
* called.
*
* The `erase()` function has two optional parameters. The first parameter
* sets the strength of erasing by the shape's interior. A value of 0 means
* that no erasing will occur. A value of 255 means that the shape's interior
* will fully erase the content underneath. The default value is 255
* (full strength).
*
* The second parameter sets the strength of erasing by the shape's edge. A
* value of 0 means that no erasing will occur. A value of 255 means that the
* shape's edge will fully erase the content underneath. The default value is
* 255 (full strength).
*
* To cancel the erasing effect, use the noErase()
* function.
*
* `erase()` has no effect on drawing done with the
* image() and
* background() functions.
*
* @method erase
* @param {Number} [strengthFill] a number (0-255) for the strength of erasing under a shape's interior.
* Defaults to 255, which is full strength.
* @param {Number} [strengthStroke] a number (0-255) for the strength of erasing under a shape's edge.
* Defaults to 255, which is full strength.
*
* @chainable
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(100, 100, 250);
*
* // Draw a pink square.
* fill(250, 100, 100);
* square(20, 20, 60);
*
* // Erase a circular area.
* erase();
* circle(25, 30, 30);
* noErase();
*
* describe('A purple canvas with a pink square in the middle. A circle is erased from the top-left, leaving a hole.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(100, 100, 250);
*
* // Draw a pink square.
* fill(250, 100, 100);
* square(20, 20, 60);
*
* // Erase a circular area.
* strokeWeight(5);
* erase(150, 255);
* circle(25, 30, 30);
* noErase();
*
* describe('A purple canvas with a pink square in the middle. A circle at the top-left partially erases its interior and a fully erases its outline.');
* }
*/
fn.erase = function(opacityFill = 255, opacityStroke = 255) {
this._renderer.erase(opacityFill, opacityStroke);
return this;
};
/**
* Ends erasing that was started with erase().
*
* The fill(), stroke(), and
* blendMode() settings will return to what they
* were prior to calling erase().
*
* @method noErase
* @chainable
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(235, 145, 15);
*
* // Draw the left rectangle.
* noStroke();
* fill(30, 45, 220);
* rect(30, 10, 10, 80);
*
* // Erase a circle.
* erase();
* circle(50, 50, 60);
* noErase();
*
* // Draw the right rectangle.
* rect(70, 10, 10, 80);
*
* describe('An orange canvas with two tall blue rectangles. A circular hole in the center erases the rectangle on the left but not the one on the right.');
* }
*/
fn.noErase = function() {
this._renderer.noErase();
return this;
};
/**
* Sets the way colors blend when added to the canvas.
*
* By default, drawing with a solid color paints over the current pixel values
* on the canvas. `blendMode()` offers many options for blending colors.
*
* Shapes, images, and text can be used as sources for drawing to the canvas.
* A source pixel changes the color of the canvas pixel where it's drawn. The
* final color results from blending the source pixel's color with the canvas
* pixel's color. RGB color values from the source and canvas pixels are
* compared, added, subtracted, multiplied, and divided to create different
* effects. Red values with red values, greens with greens, and blues with
* blues.
*
* The parameter, `mode`, sets the blend mode. For example, calling
* `blendMode(ADD)` sets the blend mode to `ADD`. The following blend modes
* are available in both 2D and WebGL mode:
*
* - `BLEND`: color values from the source overwrite the canvas. This is the default mode.
* - `ADD`: color values from the source are added to values from the canvas.
* - `DARKEST`: keeps the darkest color value.
* - `LIGHTEST`: keeps the lightest color value.
* - `EXCLUSION`: similar to `DIFFERENCE` but with less contrast.
* - `MULTIPLY`: color values from the source are multiplied with values from the canvas. The result is always darker.
* - `SCREEN`: all color values are inverted, then multiplied, then inverted again. The result is always lighter. (Opposite of `MULTIPLY`)
* - `REPLACE`: the last source drawn completely replaces the rest of the canvas.
* - `REMOVE`: overlapping pixels are removed by making them completely transparent.
*
* The following blend modes are only available in 2D mode:
*
* - `DIFFERENCE`: color values from the source are subtracted from the values from the canvas. If the difference is a negative number, it's made positive.
* - `OVERLAY`: combines `MULTIPLY` and `SCREEN`. Dark values in the canvas get darker and light values get lighter.
* - `HARD_LIGHT`: combines `MULTIPLY` and `SCREEN`. Dark values in the source get darker and light values get lighter.
* - `SOFT_LIGHT`: a softer version of `HARD_LIGHT`.
* - `DODGE`: lightens light tones and increases contrast. Divides the canvas color values by the inverted color values from the source.
* - `BURN`: darkens dark tones and increases contrast. Divides the source color values by the inverted color values from the canvas, then inverts the result.
*
* The following blend modes are only available in WebGL mode:
*
* - `SUBTRACT`: RGB values from the source are subtracted from the values from the canvas. If the difference is a negative number, it's made positive. Alpha (transparency) values from the source and canvas are added.
*
* @method blendMode
* @param {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|REMOVE|SUBTRACT)} mode blend mode to set.
* either BLEND, DARKEST, LIGHTEST, DIFFERENCE, MULTIPLY,
* EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
* SOFT_LIGHT, DODGE, BURN, ADD, REMOVE or SUBTRACT
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Use the default blend mode.
* blendMode(BLEND);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A blue line and a red line form an X on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(ADD);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint magenta.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(DARKEST);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A blue line and a red line form an X on a gray background. The area where they overlap is black.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(LIGHTEST);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint magenta.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(EXCLUSION);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A yellow line and a cyan line form an X on a gray background. The area where they overlap is green.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(MULTIPLY);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A blue line and a red line form an X on a gray background. The area where they overlap is black.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(SCREEN);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint magenta.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(REPLACE);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A diagonal red line.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(REMOVE);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('The silhouette of an X is missing from a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(DIFFERENCE);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A yellow line and a cyan line form an X on a gray background. The area where they overlap is green.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(OVERLAY);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is bright magenta.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(HARD_LIGHT);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A blue line and a red line form an X on a gray background.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(SOFT_LIGHT);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is violet.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(DODGE);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint violet.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(BURN);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A blue line and a red line form an X on a gray background. The area where they overlap is black.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Set the blend mode.
* blendMode(SUBTRACT);
*
* // Style the lines.
* strokeWeight(30);
*
* // Draw the blue line.
* stroke('blue');
* line(25, 25, 75, 75);
*
* // Draw the red line.
* stroke('red');
* line(75, 25, 25, 75);
*
* describe('A yellow line and a turquoise line form an X on a gray background. The area where they overlap is green.');
* }
*/
fn.blendMode = function (mode) {
// p5._validateParameters('blendMode', arguments);
if (mode === NORMAL) {
// Warning added 3/26/19, can be deleted in future (1.0 release?)
console.warn(
'NORMAL has been deprecated for use in blendMode. defaulting to BLEND instead.'
);
mode = BLEND;
}
this._renderer.blendMode(mode);
};
}
if(typeof p5 !== 'undefined'){
setting(p5, p5.prototype);
}
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var GifReader_1;
var GifWriter_1;
function GifWriter(buf, width, height, gopts) {
var p = 0;
var gopts = gopts === undefined ? { } : gopts;
var loop_count = gopts.loop === undefined ? null : gopts.loop;
var global_palette = gopts.palette === undefined ? null : gopts.palette;
if (width <= 0 || height <= 0 || width > 65535 || height > 65535)
throw new Error("Width/Height invalid.");
function check_palette_and_num_colors(palette) {
var num_colors = palette.length;
if (num_colors < 2 || num_colors > 256 || num_colors & (num_colors-1)) {
throw new Error(
"Invalid code/color length, must be power of 2 and 2 .. 256.");
}
return num_colors;
}
// - Header.
buf[p++] = 0x47; buf[p++] = 0x49; buf[p++] = 0x46; // GIF
buf[p++] = 0x38; buf[p++] = 0x39; buf[p++] = 0x61; // 89a
// Handling of Global Color Table (palette) and background index.
var gp_num_colors_pow2 = 0;
var background = 0;
if (global_palette !== null) {
var gp_num_colors = check_palette_and_num_colors(global_palette);
while (gp_num_colors >>= 1) ++gp_num_colors_pow2;
gp_num_colors = 1 << gp_num_colors_pow2;
--gp_num_colors_pow2;
if (gopts.background !== undefined) {
background = gopts.background;
if (background >= gp_num_colors)
throw new Error("Background index out of range.");
// The GIF spec states that a background index of 0 should be ignored, so
// this is probably a mistake and you really want to set it to another
// slot in the palette. But actually in the end most browsers, etc end
// up ignoring this almost completely (including for dispose background).
if (background === 0)
throw new Error("Background index explicitly passed as 0.");
}
}
// - Logical Screen Descriptor.
// NOTE(deanm): w/h apparently ignored by implementations, but set anyway.
buf[p++] = width & 0xff; buf[p++] = width >> 8 & 0xff;
buf[p++] = height & 0xff; buf[p++] = height >> 8 & 0xff;
// NOTE: Indicates 0-bpp original color resolution (unused?).
buf[p++] = (global_palette !== null ? 0x80 : 0) | // Global Color Table Flag.
gp_num_colors_pow2; // NOTE: No sort flag (unused?).
buf[p++] = background; // Background Color Index.
buf[p++] = 0; // Pixel aspect ratio (unused?).
// - Global Color Table
if (global_palette !== null) {
for (var i = 0, il = global_palette.length; i < il; ++i) {
var rgb = global_palette[i];
buf[p++] = rgb >> 16 & 0xff;
buf[p++] = rgb >> 8 & 0xff;
buf[p++] = rgb & 0xff;
}
}
if (loop_count !== null) { // Netscape block for looping.
if (loop_count < 0 || loop_count > 65535)
throw new Error("Loop count invalid.")
// Extension code, label, and length.
buf[p++] = 0x21; buf[p++] = 0xff; buf[p++] = 0x0b;
// NETSCAPE2.0
buf[p++] = 0x4e; buf[p++] = 0x45; buf[p++] = 0x54; buf[p++] = 0x53;
buf[p++] = 0x43; buf[p++] = 0x41; buf[p++] = 0x50; buf[p++] = 0x45;
buf[p++] = 0x32; buf[p++] = 0x2e; buf[p++] = 0x30;
// Sub-block
buf[p++] = 0x03; buf[p++] = 0x01;
buf[p++] = loop_count & 0xff; buf[p++] = loop_count >> 8 & 0xff;
buf[p++] = 0x00; // Terminator.
}
var ended = false;
this.addFrame = function(x, y, w, h, indexed_pixels, opts) {
if (ended === true) { --p; ended = false; } // Un-end.
opts = opts === undefined ? { } : opts;
// TODO(deanm): Bounds check x, y. Do they need to be within the virtual
// canvas width/height, I imagine?
if (x < 0 || y < 0 || x > 65535 || y > 65535)
throw new Error("x/y invalid.")
if (w <= 0 || h <= 0 || w > 65535 || h > 65535)
throw new Error("Width/Height invalid.")
if (indexed_pixels.length < w * h)
throw new Error("Not enough pixels for the frame size.");
var using_local_palette = true;
var palette = opts.palette;
if (palette === undefined || palette === null) {
using_local_palette = false;
palette = global_palette;
}
if (palette === undefined || palette === null)
throw new Error("Must supply either a local or global palette.");
var num_colors = check_palette_and_num_colors(palette);
// Compute the min_code_size (power of 2), destroying num_colors.
var min_code_size = 0;
while (num_colors >>= 1) ++min_code_size;
num_colors = 1 << min_code_size; // Now we can easily get it back.
var delay = opts.delay === undefined ? 0 : opts.delay;
// From the spec:
// 0 - No disposal specified. The decoder is
// not required to take any action.
// 1 - Do not dispose. The graphic is to be left
// in place.
// 2 - Restore to background color. The area used by the
// graphic must be restored to the background color.
// 3 - Restore to previous. The decoder is required to
// restore the area overwritten by the graphic with
// what was there prior to rendering the graphic.
// 4-7 - To be defined.
// NOTE(deanm): Dispose background doesn't really work, apparently most
// browsers ignore the background palette index and clear to transparency.
var disposal = opts.disposal === undefined ? 0 : opts.disposal;
if (disposal < 0 || disposal > 3) // 4-7 is reserved.
throw new Error("Disposal out of range.");
var use_transparency = false;
var transparent_index = 0;
if (opts.transparent !== undefined && opts.transparent !== null) {
use_transparency = true;
transparent_index = opts.transparent;
if (transparent_index < 0 || transparent_index >= num_colors)
throw new Error("Transparent color index.");
}
if (disposal !== 0 || use_transparency || delay !== 0) {
// - Graphics Control Extension
buf[p++] = 0x21; buf[p++] = 0xf9; // Extension / Label.
buf[p++] = 4; // Byte size.
buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0);
buf[p++] = delay & 0xff; buf[p++] = delay >> 8 & 0xff;
buf[p++] = transparent_index; // Transparent color index.
buf[p++] = 0; // Block Terminator.
}
// - Image Descriptor
buf[p++] = 0x2c; // Image Seperator.
buf[p++] = x & 0xff; buf[p++] = x >> 8 & 0xff; // Left.
buf[p++] = y & 0xff; buf[p++] = y >> 8 & 0xff; // Top.
buf[p++] = w & 0xff; buf[p++] = w >> 8 & 0xff;
buf[p++] = h & 0xff; buf[p++] = h >> 8 & 0xff;
// NOTE: No sort flag (unused?).
// TODO(deanm): Support interlace.
buf[p++] = using_local_palette === true ? (0x80 | (min_code_size-1)) : 0;
// - Local Color Table
if (using_local_palette === true) {
for (var i = 0, il = palette.length; i < il; ++i) {
var rgb = palette[i];
buf[p++] = rgb >> 16 & 0xff;
buf[p++] = rgb >> 8 & 0xff;
buf[p++] = rgb & 0xff;
}
}
p = GifWriterOutputLZWCodeStream(
buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels);
return p;
};
this.end = function() {
if (ended === false) {
buf[p++] = 0x3b; // Trailer.
ended = true;
}
return p;
};
this.getOutputBuffer = function() { return buf; };
this.setOutputBuffer = function(v) { buf = v; };
this.getOutputBufferPosition = function() { return p; };
this.setOutputBufferPosition = function(v) { p = v; };
}
// Main compression routine, palette indexes -> LZW code stream.
// |index_stream| must have at least one entry.
function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) {
buf[p++] = min_code_size;
var cur_subblock = p++; // Pointing at the length field.
var clear_code = 1 << min_code_size;
var code_mask = clear_code - 1;
var eoi_code = clear_code + 1;
var next_code = eoi_code + 1;
var cur_code_size = min_code_size + 1; // Number of bits per code.
var cur_shift = 0;
// We have at most 12-bit codes, so we should have to hold a max of 19
// bits here (and then we would write out).
var cur = 0;
function emit_bytes_to_buffer(bit_block_size) {
while (cur_shift >= bit_block_size) {
buf[p++] = cur & 0xff;
cur >>= 8; cur_shift -= 8;
if (p === cur_subblock + 256) { // Finished a subblock.
buf[cur_subblock] = 255;
cur_subblock = p++;
}
}
}
function emit_code(c) {
cur |= c << cur_shift;
cur_shift += cur_code_size;
emit_bytes_to_buffer(8);
}
// I am not an expert on the topic, and I don't want to write a thesis.
// However, it is good to outline here the basic algorithm and the few data
// structures and optimizations here that make this implementation fast.
// The basic idea behind LZW is to build a table of previously seen runs
// addressed by a short id (herein called output code). All data is
// referenced by a code, which represents one or more values from the
// original input stream. All input bytes can be referenced as the same
// value as an output code. So if you didn't want any compression, you
// could more or less just output the original bytes as codes (there are
// some details to this, but it is the idea). In order to achieve
// compression, values greater then the input range (codes can be up to
// 12-bit while input only 8-bit) represent a sequence of previously seen
// inputs. The decompressor is able to build the same mapping while
// decoding, so there is always a shared common knowledge between the
// encoding and decoder, which is also important for "timing" aspects like
// how to handle variable bit width code encoding.
//
// One obvious but very important consequence of the table system is there
// is always a unique id (at most 12-bits) to map the runs. 'A' might be
// 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc. This relationship
// can be used for an effecient lookup strategy for the code mapping. We
// need to know if a run has been seen before, and be able to map that run
// to the output code. Since we start with known unique ids (input bytes),
// and then from those build more unique ids (table entries), we can
// continue this chain (almost like a linked list) to always have small
// integer values that represent the current byte chains in the encoder.
// This means instead of tracking the input bytes (AAAABCD) to know our
// current state, we can track the table entry for AAAABC (it is guaranteed
// to exist by the nature of the algorithm) and the next character D.
// Therefor the tuple of (table_entry, byte) is guaranteed to also be
// unique. This allows us to create a simple lookup key for mapping input
// sequences to codes (table indices) without having to store or search
// any of the code sequences. So if 'AAAA' has a table entry of 12, the
// tuple of ('AAAA', K) for any input byte K will be unique, and can be our
// key. This leads to a integer value at most 20-bits, which can always
// fit in an SMI value and be used as a fast sparse array / object key.
// Output code for the current contents of the index buffer.
var ib_code = index_stream[0] & code_mask; // Load first input index.
var code_table = { }; // Key'd on our 20-bit "tuple".
emit_code(clear_code); // Spec says first code should be a clear code.
// First index already loaded, process the rest of the stream.
for (var i = 1, il = index_stream.length; i < il; ++i) {
var k = index_stream[i] & code_mask;
var cur_key = ib_code << 8 | k; // (prev, k) unique tuple.
var cur_code = code_table[cur_key]; // buffer + k.
// Check if we have to create a new code table entry.
if (cur_code === undefined) { // We don't have buffer + k.
// Emit index buffer (without k).
// This is an inline version of emit_code, because this is the core
// writing routine of the compressor (and V8 cannot inline emit_code
// because it is a closure here in a different context). Additionally
// we can call emit_byte_to_buffer less often, because we can have
// 30-bits (from our 31-bit signed SMI), and we know our codes will only
// be 12-bits, so can safely have 18-bits there without overflow.
// emit_code(ib_code);
cur |= ib_code << cur_shift;
cur_shift += cur_code_size;
while (cur_shift >= 8) {
buf[p++] = cur & 0xff;
cur >>= 8; cur_shift -= 8;
if (p === cur_subblock + 256) { // Finished a subblock.
buf[cur_subblock] = 255;
cur_subblock = p++;
}
}
if (next_code === 4096) { // Table full, need a clear.
emit_code(clear_code);
next_code = eoi_code + 1;
cur_code_size = min_code_size + 1;
code_table = { };
} else { // Table not full, insert a new entry.
// Increase our variable bit code sizes if necessary. This is a bit
// tricky as it is based on "timing" between the encoding and
// decoder. From the encoders perspective this should happen after
// we've already emitted the index buffer and are about to create the
// first table entry that would overflow our current code bit size.
if (next_code >= (1 << cur_code_size)) ++cur_code_size;
code_table[cur_key] = next_code++; // Insert into code table.
}
ib_code = k; // Index buffer to single input k.
} else {
ib_code = cur_code; // Index buffer to sequence in code table.
}
}
emit_code(ib_code); // There will still be something in the index buffer.
emit_code(eoi_code); // End Of Information.
// Flush / finalize the sub-blocks stream to the buffer.
emit_bytes_to_buffer(1);
// Finish the sub-blocks, writing out any unfinished lengths and
// terminating with a sub-block of length 0. If we have already started
// but not yet used a sub-block it can just become the terminator.
if (cur_subblock + 1 === p) { // Started but unused.
buf[cur_subblock] = 0;
} else { // Started and used, write length and additional terminator block.
buf[cur_subblock] = p - cur_subblock - 1;
buf[p++] = 0;
}
return p;
}
function GifReader(buf) {
var p = 0;
// - Header (GIF87a or GIF89a).
if (buf[p++] !== 0x47 || buf[p++] !== 0x49 || buf[p++] !== 0x46 ||
buf[p++] !== 0x38 || (buf[p++]+1 & 0xfd) !== 0x38 || buf[p++] !== 0x61) {
throw new Error("Invalid GIF 87a/89a header.");
}
// - Logical Screen Descriptor.
var width = buf[p++] | buf[p++] << 8;
var height = buf[p++] | buf[p++] << 8;
var pf0 = buf[p++]; //