q5.js
Version:
[q5 fork] p5.js small & fast alternative (experimental) implementation.
1,813 lines (1,694 loc) • 65.8 kB
JavaScript
if (typeof exports === "object" && typeof module !== "undefined") module.exports = Q5;
function Q5(scope, attr) {
"use strict";
return new graphics(scope);
function graphics(scope) {
let $ = (scope !== "offscreen" && typeof scope !== "undefined") ? window : this;
$.canvas = document.createElement("canvas");
let ctx = $.canvas.getContext("2d", attr);
// Context Attributes Object
// : developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getContextAttributes
$.width = 100;
$.height = 100;
$.canvas.width = $.width;
$.canvas.height = $.height;
if (scope != "offscreen") {
let parent = (scope == "global" || typeof scope === "undefined")
? document.body
: document.querySelector(scope);
if (document.body) {
parent.appendChild($.canvas);
} else {
window.addEventListener("load", function () {
parent.appendChild($.canvas);
})
}
}
defaultStyle();
//================================================================
// CONSTANTS
//================================================================
$.MAGIC = 0x9A0CE55
$.RGB = 0;
$.HSV = 1;
$.HSB = 1;
$.CHORD = 0;
$.PIE = 1;
$.OPEN = 2;
$.RADIUS = 1;
$.CORNER = 2;
$.CORNERS = 3;
$.ROUND = "round";
$.SQUARE = "butt";
$.PROJECT = "square";
$.MITER = "miter";
$.BEVEL = "bevel";
$.CLOSE = 1;
$.BLEND = 'source-over';
$.REMOVE = 'destination-out';
$.ADD = 'lighter';
$.DARKEST = 'darken';
$.LIGHTEST = 'lighten';
$.DIFFERENCE = 'difference';
$.SUBTRACT = 'subtract';
$.EXCLUSION = 'exclusion';
$.MULTIPLY = 'multiply';
$.SCREEN = 'screen';
$.REPLACE = 'copy';
$.OVERLAY = 'overlay';
$.HARD_LIGHT = 'hard-light';
$.SOFT_LIGHT = 'soft-light';
$.DODGE = 'color-dodge';
$.BURN = 'color-burn';
$.NORMAL = "normal";
$.ITALIC = "italic";
$.BOLD = "bold";
$.BOLDITALIC = "italic bold";
$.CENTER = "center";
$.LEFT = "left";
$.RIGHT = "right";
$.TOP = "top";
$.BOTTOM = "bottom";
$.BASELINE = "alphabetic";
$.LANDSCAPE = "landscape";
$.PORTRAIT = "portrait";
$.ALT = 18;
$.BACKSPACE = 8;
$.CONTROL = 17;
$.DELETE = 46;
$.DOWN_ARROW = 40;
$.ENTER = 13;
$.ESCAPE = 27;
$.LEFT_ARROW = 37;
$.OPTION = 18;
$.RETURN = 13;
$.RIGHT_ARROW = 39;
$.SHIFT = 16;
$.TAB = 9;
$.UP_ARROW = 38;
$.HALF_PI = Math.PI / 2;
$.PI = Math.PI;
$.QUARTER_PI = Math.PI / 4;
$.TAU = Math.PI * 2;
$.TWO_PI = Math.PI * 2;
$.THRESHOLD = 1;
$.GRAY = 2;
$.OPAQUE = 3;
$.INVERT = 4;
$.POSTERIZE = 5;
$.DILATE = 6;
$.ERODE = 7;
$.BLUR = 8;
// Extra Filters :
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter
// https://github.com/davidenke/context-filter-polyfill
$.BRIGHTNESS = 9;
$.CONTRAST = 10;
$.SATURATE = 11;
$.ARROW = 'default';
$.CROSS = 'crosshair';
$.HAND = 'pointer';
$.MOVE = 'move';
$.TEXT = 'text';
$.VIDEO = { video: true, audio: false };
$.AUDIO = { video: false, audio: true };
$.SHR3 = 1;
$.LCG = 2;
//================================================================
// HINTS
//================================================================
$.HARDWARE_FILTERS = true;
$.hint = function (prop, val) {
$[prop] = val;
}
//================================================================
// PUBLIC PROPERTIES
//================================================================
$.frameCount = 0;
$.mouseX = 0;
$.mouseY = 0;
$.pmouseX = 0;
$.pmouseY = 0;
$.mouseButton = null;
$.keyIsPressed = false;
$.mouseIsPressed = false;
$.key = null;
$.keyCode = null;
$.pixels = null;
$.accelerationX = 0;
$.accelerationY = 0;
$.accelerationZ = 0;
$.rotationX = 0;
$.rotationY = 0;
$.rotationZ = 0;
$.relRotationX = 0;
$.relRotationY = 0;
$.relRotationZ = 0;
$.pAccelerationX = 0;
$.pAccelerationY = 0;
$.pAccelerationZ = 0;
$.pRotationX = 0;
$.pRotationY = 0;
$.pRotationZ = 0;
$.pRelRotationX = 0;
$.pRelRotationY = 0;
$.pRelRotationZ = 0;
$.touches = [];
$._styleCache = [
{
colorMode: $.RGB,
noStroke: false,
noFill: false,
ellipseMode: $.CENTER,
rectMode: $.CORNER,
curveDetail: 20,
curveAlpha: 0.0,
textFont: "sans-serif",
textSize: 12,
textLeading: 12,
textStyle: "normal"
}
]
$._style = $._styleCache[$._styleCache.length - 1]
$._noLoop = false;
$._pixelDensity = 1;
$._frameRate = null;
$._tint = null;
//================================================================
// PRIVATE VARS
//================================================================
let looper = null;
let firstVertex = true;
let curveBuff = [];
let imgData = null;
let preloadCnt = 0;
let keysHeld = {};
let millisStart = 0;
let tmpCtx = null;
let tmpCt2 = null;
let tmpBuf = null;
//================================================================
// ALIAS PROPERTIES
//================================================================
Object.defineProperty($, "deviceOrientation", {
get: function () { return Math.abs(window.orientation) == 90 ? $.LANDSCAPE : $.PORTRAIT },
});
Object.defineProperty($, "windowWidth", {
get: function () { return window.innerWidth },
});
Object.defineProperty($, "windowHeight", {
get: function () { return window.innerHeight },
});
Object.defineProperty($, "drawingContext", {
get: function () { return ctx },
});
//================================================================
// CANVAS
//================================================================
$.createCanvas = function (width, height) {
$.width = width;
$.height = height;
$.canvas.width = width;
$.canvas.height = height;
defaultStyle();
return $.canvas;
}
$.resizeCanvas = function (width, height) {
$.width = width;
$.height = height;
$.canvas.width = width;
$.canvas.height = height;
}
$.createGraphics = $.createImage = function (width, height) {
let g = new graphics("offscreen");
g.createCanvas(width, height);
g.noLoop();
return g;
}
$.pixelDensity = function (n) {
if (n == undefined) {
return $._pixelDensity;
}
$._pixelDensity = n;
$.canvas.width = Math.ceil($.width * n);
$.canvas.height = Math.ceil($.height * n);
$.canvas.style.width = $.width + "px";
$.canvas.style.height = $.height + "px";
ctx.scale($._pixelDensity, $._pixelDensity);
defaultStyle();
return $._pixelDensity;
}
//================================================================
// MATH
//================================================================
$.map = function (value, istart, istop, ostart, ostop, clamp) {
let val = ostart + (ostop - ostart) * ((value - istart) * 1.0 / (istop - istart));
if (!clamp) {
return val;
}
if (ostart < ostop) {
return Math.min(Math.max(val, ostart), ostop);
} else {
return Math.min(Math.max(val, ostop), ostart);
}
}
$.lerp = function (a, b, t) {
return a * (1 - t) + b * t;
}
$.constrain = function (x, lo, hi) {
return Math.min(Math.max(x, lo), hi);
}
$.dist = function () {
if (arguments.length == 4) {
return Math.hypot(arguments[0] - arguments[2], arguments[1] - arguments[3]);
} else {
return Math.hypot(arguments[0] - arguments[3], arguments[1] - arguments[4], arguments[2] - arguments[5]);
}
}
$.norm = function (value, start, stop) {
return $.map(value, start, stop, 0, 1);
}
$.sq = function (x) {
return x * x;
}
$.fract = function (x) {
return x - Math.floor(x);
}
$.degrees = function (x) {
return x * 180 / Math.PI;
}
$.radians = function (x) {
return x * Math.PI / 180;
}
$.abs = Math.abs;
$.ceil = Math.ceil;
$.exp = Math.exp;
$.floor = Math.floor;
$.log = Math.log;
$.mag = Math.hypot;
$.max = Math.max;
$.min = Math.min;
$.round = Math.round;
$.sqrt = Math.sqrt;
$.sin = Math.sin;
$.cos = Math.cos;
$.tan = Math.tan;
$.asin = Math.asin;
$.acos = Math.acos;
$.atan = Math.atan;
$.atan2 = Math.atan2;
//================================================================
// VECTOR
//================================================================
$.Vector = function (_x, _y, _z) {
let v = this;
v.x = _x || 0;
v.y = _y || 0;
v.z = _z || 0;
let cacheNorm = null;
let cacheNormSq = null;
v.set = function (_x, _y, _z) {
v.x = _x || 0;
v.y = _y || 0;
v.z = _z || 0;
}
v.copy = function () {
return new $.Vector(v.x, v.y, v.z);
}
function arg2v(x, y, z) {
if (x.x != undefined) {
return x;
}
if (y != undefined) {
return { x, y, z: z || 0 }
}
return { x: x, y: x, z: x };
}
function calcNorm() {
if (cacheNormSq == null) {
cacheNormSq = v.x * v.x + v.y * v.y + v.z * v.z;
cacheNorm = Math.sqrt(cacheNormSq);
}
}
function deprecNorm() {
cacheNormSq = null;
cacheNorm = null;
}
v.add = function () { let u = arg2v.apply(null, arguments); v.x += u.x; v.y += u.y; v.z += u.z; deprecNorm(); return v }
v.rem = function () { let u = arg2v.apply(null, arguments); v.x %= u.x; v.y %= u.y; v.z %= u.z; deprecNorm(); return v }
v.sub = function () { let u = arg2v.apply(null, arguments); v.x -= u.x; v.y -= u.y; v.z -= u.z; deprecNorm(); return v }
v.mult = function () { let u = arg2v.apply(null, arguments); v.x *= u.x; v.y *= u.y; v.z *= u.z; deprecNorm(); return v }
v.div = function () { let u = arg2v.apply(null, arguments); v.x /= u.x; v.y /= u.y; v.z /= u.z; deprecNorm(); return v }
v.mag = function () { calcNorm(); return cacheNorm }
v.magSq = function () { calcNorm(); return cacheNormSq }
v.dot = function () { let u = arg2v.apply(null, arguments); return v.x * u.x + v.y * u.y + v.z * u.z }
v.dist = function () { let u = arg2v.apply(null, arguments); let x = v.x - u.x; let y = v.y - u.y; let z = v.z - u.z; return Math.sqrt(x * x + y * y + z * z) }
v.cross = function () {
let u = arg2v.apply(null, arguments);
let x = v.y * u.z - v.z * u.y;
let y = v.z * u.x - v.x * u.z;
let z = v.x * u.y - v.y * u.x;
v.x = x; v.y = y; v.z = z;
deprecNorm();
return v;
}
v.normalize = function () {
calcNorm();
let n = cacheNorm;
v.x /= n; v.y /= n; v.z /= n;
cacheNorm = 1;
cacheNormSq = 1;
return v;
}
v.limit = function (m) {
calcNorm();
let n = cacheNorm;
if (n > m) {
let t = m / n;
v.x *= t; v.y *= t; v.z *= t;
cacheNorm = m;
cacheNormSq = m * m;
}
return v;
}
v.setMag = function (m) {
calcNorm();
let n = cacheNorm;
let t = m / n;
v.x *= t; v.y *= t; v.z *= t;
cacheNorm = m;
cacheNormSq = m * m;
return v;
}
v.heading = function () {
return Math.atan2(v.y, v.x);
}
v.rotate = function (ang) {
let costh = Math.cos(ang);
let sinth = Math.sin(ang);
let vx = (v.x * costh - v.y * sinth);
let vy = (v.x * sinth + v.y * costh);
v.x = vx;
v.y = vy;
return v;
}
v.angleBetween = function () {
let u = arg2v.apply(null, arguments);
const costh = v.dot(u) / (v.mag() * u.mag());
let ang;
ang = Math.acos(Math.min(1, Math.max(-1, costh)));
ang = ang * Math.sign(v.cross(u).z || 1);
return ang;
}
v.lerp = function (u, t) {
v.x = v.x * (1 - t) + u.x * t;
v.y = v.y * (1 - t) + u.y * t;
v.z = v.z * (1 - t) + u.z * t;
deprecNorm();
return v;
}
v.reflect = function (n) {
n.normalize();
return v.sub(n.mult(2 * v.dot(n)));
}
v.array = function () {
return [v.x, v.y, v.z];
}
v.equals = function (u, epsilon) {
if (epsilon == undefined) {
epsilon = Number.EPSILON;
if (epsilon == undefined) {
epsilon = 0;
}
}
return Math.abs(u.x - v.x) < epsilon
&& Math.abs(u.y - v.y) < epsilon
&& Math.abs(u.z - v.z) < epsilon;
}
v.fromAngle = function (th, l) {
if (l == undefined) {
l = 1;
}
cacheNorm = l;
cacheNormSq = l * l;
v.x = l * Math.cos(th);
v.y = l * Math.sin(th);
v.z = 0;
return v;
}
v.fromAngles = function (th, ph, l) {
if (l == undefined) {
l = 1;
}
cacheNorm = l;
cacheNormSq = l * l;
const cosph = Math.cos(ph);
const sinph = Math.sin(ph);
const costh = Math.cos(th);
const sinth = Math.sin(th);
v.x = l * sinth * sinph;
v.y = -l * costh;
v.z = l * sinth * cosph;
return v;
}
v.random2D = function () {
cacheNorm = 1;
cacheNormSq = 1;
return v.fromAngle(Math.random() * Math.PI * 2);
}
v.random3D = function () {
cacheNorm = 1;
cacheNormSq = 1;
return v.fromAngles(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2);
}
v.toString = function () {
return `[${v.x}, ${v.y}, ${v.z}]`
}
}
$.Vector.add = function (v, u) { return new $.Vector(v.x + u.x, v.y + u.y, v.z + u.z) }
$.Vector.rem = function (v, u) { return new $.Vector(v.x % u.x, v.y % u.y, v.z % u.z) }
$.Vector.sub = function (v, u) { return new $.Vector(v.x - u.x, v.y - u.y, v.z - u.z) }
$.Vector.mult = function (v, u) { if (u.x == undefined) { return new $.Vector(v.x * u, v.y * u, v.z * u) }; return new $.Vector(v.x * u.x, v.y * u.y, v.z * u.z) }
$.Vector.div = function (v, u) { if (u.x == undefined) { return new $.Vector(v.x / u, v.y / u, v.z / u) }; return new $.Vector(v.x / u.x, v.y / u.y, v.z / u.z) }
$.Vector.dist = function (v, u) { return Math.hypot(v.x - u.x, v.y - u.y, v.z - u.z) }
$.Vector.cross = function (v, u) { return new $.Vector(v.y * u.z - v.z * u.y, v.z * u.x - v.x * u.z, v.x * u.y - v.y * u.x); }
$.Vector.lerp = function (v, u, t) {
return new $.Vector(v.x * (1 - t) + u.x * t,
v.y = v.y * (1 - t) + u.y * t,
v.z = v.z * (1 - t) + u.z * t);
}
$.Vector.equals = function (v, u, epsilon) { return v.equals(u, epsilon) }
for (let k of ["fromAngle", "fromAngles", "random2D", "random3D"]) {
$.Vector[k] = function (u, v, t) {
return (new $.Vector())[k](u, v, t);
}
}
$.createVector = function (x, y, z) {
return new $.Vector(x, y, z);
}
//================================================================
// CURVE QUERY
//================================================================
//https://github.com/processing/p5.js/blob/1.1.9/src/core/shape/curves.js
$.curvePoint = function (a, b, c, d, t) {
const t3 = t * t * t,
t2 = t * t,
f1 = -0.5 * t3 + t2 - 0.5 * t,
f2 = 1.5 * t3 - 2.5 * t2 + 1.0,
f3 = -1.5 * t3 + 2.0 * t2 + 0.5 * t,
f4 = 0.5 * t3 - 0.5 * t2;
return a * f1 + b * f2 + c * f3 + d * f4;
}
$.bezierPoint = function (a, b, c, d, t) {
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
);
}
$.curveTangent = function (a, b, c, d, t) {
const t2 = t * t,
f1 = -3 * t2 / 2 + 2 * t - 0.5,
f2 = 9 * t2 / 2 - 5 * t,
f3 = -9 * t2 / 2 + 4 * t + 0.5,
f4 = 3 * t2 / 2 - t;
return a * f1 + b * f2 + c * f3 + d * f4;
};
$.bezierTangent = function (a, b, c, d, t) {
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)
);
};
//================================================================
// COLORS
//================================================================
function hsv2rgb(h, s, v) {
//https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
let r, g, b;
let hh, i, ff, p, q, t;
if (s == 0) {
r = v;
g = v;
b = v;
return [r * 255, g * 255, b * 255];
}
hh = h;
if (hh > 360) hh = 0;
hh /= 60;
i = ~~hh;
ff = hh - i;
p = v * (1.0 - s);
q = v * (1.0 - (s * ff));
t = v * (1.0 - (s * (1.0 - ff)));
switch (i) {
case 0:
r = v; g = t; b = p;
break;
case 1:
r = q; g = v; b = p;
break;
case 2:
r = p; g = v; b = t;
break;
case 3:
r = p; g = q; b = v;
break;
case 4:
r = t; g = p; b = v;
break;
default:
r = v; g = p; b = q;
break;
}
return [r * 255, g * 255, b * 255];
}
function rgb2hsv(r, g, b) {
//https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
let rgbMin, rgbMax;
let h, s, v;
rgbMin = r < g ? (r < b ? r : b) : (g < b ? g : b);
rgbMax = r > g ? (r > b ? r : b) : (g > b ? g : b);
v = rgbMax * 100 / 255;
if (v == 0) {
h = 0;
s = 0;
return [h, s, v];
}
s = 100 * (rgbMax - rgbMin) / rgbMax;
if (s == 0) {
h = 0;
return [h, s, v];
}
if (rgbMax == r)
h = 0 + 60 * (g - b) / (rgbMax - rgbMin);
else if (rgbMax == g)
h = 120 + 60 * (b - r) / (rgbMax - rgbMin);
else
h = 240 + 60 * (r - g) / (rgbMax - rgbMin);
return [h, s, v];
}
$.Color = function (r, g, b, a) {
let that = this;
that.MAGIC = 0xC010A;
that._r = r;
that._g = g;
that._b = b;
that._a = a;
that._h = 0;
that._s = 0;
that._v = 0;
that._hsvInferred = false;
that.setRed = function (x) {
that._r = x;
that._hsvInferred = false;
}
that.setGreen = function (x) {
that._g = x;
that._hsvInferred = false;
}
that.setBlue = function (x) {
that._b = x;
that._hsvInferred = false;
}
that.setAlpha = function (x) {
that._a = x / 255;
that._hsvInferred = false;
}
that._inferHSV = function () {
if (!that._hsvInferred) {
[that._h, that._s, that._v] = rgb2hsv(that._r, that._g, that._b);
that._hsvInferred = true;
}
}
that.toString = function () {
return `rgba(${Math.round(that._r)},${Math.round(that._g)},${Math.round(that._b)},${(~~(that._a * 1000)) / 1000})`
}
}
$.colorMode = function (mode) {
$._style.colorMode = mode;
}
$.color = function () {
if (arguments.length == 1 && arguments[0].MAGIC == 0xC010A) {
return arguments[0];
}
if ($._style.colorMode == $.RGB) {
if (arguments.length == 1) {
return new $.Color(arguments[0], arguments[0], arguments[0], 1);
} else if (arguments.length == 2) {
return new $.Color(arguments[0], arguments[0], arguments[0], arguments[1] / 255);
} else if (arguments.length == 3) {
return new $.Color(arguments[0], arguments[1], arguments[2], 1);
} else if (arguments.length == 4) {
return new $.Color(arguments[0], arguments[1], arguments[2], arguments[3] / 255);
}
} else {
if (arguments.length == 1) {
return new $.Color(...hsv2rgb(0, 0, arguments[0] / 100), 1);
} else if (arguments.length == 2) {
return new $.Color(...hsv2rgb(0, 0, arguments[0] / 100), arguments[1] / 255);
} else if (arguments.length == 3) {
return new $.Color(...hsv2rgb(arguments[0], arguments[1] / 100, arguments[2] / 100), 1);
} else if (arguments.length == 4) {
return new $.Color(...hsv2rgb(arguments[0], arguments[1] / 100, arguments[2] / 100), arguments[3]);
}
}
return null;
}
$.red = function (c) {
return c._r;
}
$.green = function (c) {
return c._g;
}
$.blue = function (c) {
return c._b;
}
$.alpha = function (c) {
return c._a * 255;
}
$.hue = function (c) {
c._inferHSV();
return c._h;
}
$.saturation = function (c) {
c._inferHSV();
return c._s;
}
$.brightness = function (c) {
c._inferHSV();
return c._v;
}
$.lightness = function (c) {
return (0.2126 * c._r + 0.7152 * c._g + 0.0722 * c._b) * 100 / 255;
}
function lerpHue(h0, h1, t) {
var methods = [
[Math.abs(h1 - h0), $.map(t, 0, 1, h0, h1)],
[Math.abs(h1 + 360 - h0), $.map(t, 0, 1, h0, h1 + 360)],
[Math.abs(h1 - 360 - h0), $.map(t, 0, 1, h0, h1 - 360)]
]
methods.sort((x, y) => (x[0] - y[0]))
return (methods[0][1] + 720) % 360;
}
$.lerpColor = function (a, b, t) {
if ($._style.colorMode == $.RGB) {
return new $.Color(
$.constrain($.lerp(a._r, b._r, t), 0, 255),
$.constrain($.lerp(a._g, b._g, t), 0, 255),
$.constrain($.lerp(a._b, b._b, t), 0, 255),
$.constrain($.lerp(a._a, b._a, t), 0, 1),
)
} else {
a._inferHSV();
b._inferHSV();
return new $.Color(
$.constrain(lerpHue(a._h, b._h, t), 0, 360),
$.constrain($.lerp(a._s, b._s, t), 0, 100),
$.constrain($.lerp(a._v, b._v, t), 0, 100),
$.constrain($.lerp(a._a, b._a, t), 0, 1),
)
}
}
//================================================================
// DRAWING SETTING
//================================================================
function defaultStyle() {
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.lineCap = "round";
ctx.lineJoin = "miter";
}
$.strokeWeight = function (n) {
$._style_noStroke = false;
ctx.lineWidth = n;
}
$.stroke = function () {
$._style.noStroke = false;
if (typeof arguments[0] == "string") {
ctx.strokeStyle = arguments[0];
return;
}
let col = $.color.apply(null, arguments);
if (col._a <= 0) {
$._style.noStroke = true;
return;
}
ctx.strokeStyle = col;
}
$.noStroke = function () {
$._style.noStroke = true;
}
$.fill = function () {
$._style.noFill = false;
if (typeof arguments[0] == "string") {
ctx.fillStyle = arguments[0];
return;
}
let col = $.color.apply(null, arguments);
if (col._a <= 0) {
$._style.noFill = true;
return;
}
ctx.fillStyle = col;
}
$.noFill = function () {
$._style.noFill = true;
}
$.blendMode = function (x) {
ctx.globalCompositeOperation = x;
}
$.strokeCap = function (x) { ctx.lineCap = x; }
$.strokeJoin = function (x) { ctx.lineJoin = x; }
$.ellipseMode = function (x) { $._style.ellipseMode = x; }
$.rectMode = function (x) { $._style.rectMode = x; }
$.curveDetail = function (x) { $._style.curveDetail = x; }
$.curveAlpha = function (x) { $._style.curveAlpha = x; }
$.curveTightness = function (x) {
console.warn("curveTightness() sets the 'alpha' parameter of Catmull-Rom curve, and is NOT identical to p5.js counterpart. As this might change in the future, please call curveAlpha() directly.");
$._style.curveAlpha = x;
}
//================================================================
// DRAWING
//================================================================
$.clear = function () {
ctx.clearRect(0, 0, $.width, $.height);
}
$.background = function () {
if (arguments[0] && arguments[0].MAGIC == $.MAGIC) {
return $.image(arguments[0], 0, 0, $.width, $.height);
}
ctx.save();
ctx.resetTransform();
ctx.scale($._pixelDensity, $._pixelDensity);
if (typeof arguments[0] == "string") {
ctx.fillStyle = arguments[0];
} else {
ctx.fillStyle = $.color(...Array.from(arguments));
}
ctx.fillRect(0, 0, $.width, $.height);
ctx.restore();
}
$.line = function (x0, y0, x1, y1) {
if (!$._style.noStroke) {
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
}
}
function norm2PI(x) {
if (0 <= x && x < Math.PI * 2) {
return x;
}
while (x < 0) {
x += Math.PI * 2;
}
while (x >= Math.PI) {
x -= Math.PI * 2;
}
return x;
}
function arcImpl(x, y, w, h, start, stop, mode, detail) {
if ($._style.noFill && $._style.noStroke) {
return;
}
let lo = norm2PI(start);
let hi = norm2PI(stop);
ctx.beginPath();
for (let i = 0; i < detail + 1; i++) {
let t = i / detail;
let a = $.lerp(lo, hi, t);
let dx = Math.cos(a) * w / 2;
let dy = Math.sin(a) * h / 2;
ctx[i ? "lineTo" : "moveTo"](x + dx, y + dy);
}
if (mode == $.CHORD) {
ctx.closePath();
} else if (mode == $.PIE) {
ctx.lineTo(x, y);
ctx.closePath();
}
if (!$._style.noFill) ctx.fill();
if (!$._style.noStroke) ctx.stroke();
}
$.arc = function (x, y, w, h, start, stop, mode, detail) {
if (start == stop) {
return $.ellipse(x, y, w, h);
}
if (detail == undefined) {
detail = 25;
}
if (mode == undefined) {
mode = $.PIE;
}
if ($._style.ellipseMode == $.CENTER) {
arcImpl(x, y, w, h, start, stop, mode, detail);
} else if ($._style.ellipseMode == $.RADIUS) {
arcImpl(x, y, w * 2, h * 2, start, stop, mode, detail);
} else if ($._style.ellipseMode == $.CORNER) {
arcImpl(x + w / 2, y + h / 2, w, h, start, stop, mode, detail);
} else if ($._style.ellipseMode == $.CORNERS) {
arcImpl((x + w) / 2, (y + h) / 2, (w - x), (h - y), start, stop, mode, detail);
}
}
function ellipseImpl(x, y, w, h) {
if ($._style.noFill && $._style.noStroke) {
return;
}
ctx.beginPath();
ctx.ellipse(x, y, w / 2, h / 2, 0, 0, Math.PI * 2);
if (!$._style.noFill) ctx.fill();
if (!$._style.noStroke) ctx.stroke();
}
$.ellipse = function (x, y, w, h) {
if (h == undefined) {
h = w;
}
if ($._style.ellipseMode == $.CENTER) {
ellipseImpl(x, y, w, h);
} else if ($._style.ellipseMode == $.RADIUS) {
ellipseImpl(x, y, w * 2, h * 2);
} else if ($._style.ellipseMode == $.CORNER) {
ellipseImpl(x + w / 2, y + h / 2, w, h);
} else if ($._style.ellipseMode == $.CORNERS) {
ellipseImpl((x + w) / 2, (y + h) / 2, (w - x), (h - y));
}
}
$.circle = function (x, y, r) {
return $.ellipse(x, y, r, r);
}
$.point = function (x, y) {
if (x.x) {
y = x.y;
x = x.x;
}
ctx.beginPath();
ctx.ellipse(x, y, 0.4, 0.4, 0, 0, Math.PI * 2);
ctx.stroke();
}
function rectImpl(x, y, w, h) {
if (!$._style.noFill) {
ctx.fillRect(x, y, w, h);
}
if (!$._style.noStroke) {
ctx.strokeRect(x, y, w, h);
}
}
function roundedRectImpl(x, y, w, h, tl, tr, br, bl) {
if ($._style.noFill && $._style.noStroke) {
return;
}
if (tl == undefined) {
return rectImpl(x, y, w, h);
}
if (tr == undefined) {
return roundedRectImpl(x, y, w, h, tl, tl, tl, tl);
}
const hh = Math.min(Math.abs(h), Math.abs(w)) / 2;
tl = Math.min(hh, tl);
tr = Math.min(hh, tr);
bl = Math.min(hh, bl);
br = Math.min(hh, br);
ctx.beginPath();
ctx.moveTo(x + tl, y);
ctx.arcTo(x + w, y, x + w, y + h, tr);
ctx.arcTo(x + w, y + h, x, y + h, br);
ctx.arcTo(x, y + h, x, y, bl);
ctx.arcTo(x, y, x + w, y, tl);
ctx.closePath();
if (!$._style.noFill) ctx.fill();
if (!$._style.noStroke) ctx.stroke();
}
$.rect = function (x, y, w, h, tl, tr, br, bl) {
if ($._style.rectMode == $.CENTER) {
roundedRectImpl(x - w / 2, y - h / 2, w, h, tl, tr, br, bl);
} else if ($._style.rectMode == $.RADIUS) {
roundedRectImpl(x - w, y - h, w * 2, h * 2, tl, tr, br, bl);
} else if ($._style.rectMode == $.CORNER) {
roundedRectImpl(x, y, w, h, tl, tr, br, bl);
} else if ($._style.rectMode == $.CORNERS) {
roundedRectImpl(x, y, w - x, h - y, tl, tr, br, bl);
}
}
$.square = function (x, y, s, tl, tr, br, bl) {
return $.rect(x, y, s, s, tl, tr, br, bl);
}
function clearBuff() {
curveBuff = [];
}
$.beginShape = function () {
clearBuff();
ctx.beginPath();
firstVertex = true;
}
$.beginContour = function () {
ctx.closePath();
clearBuff();
firstVertex = true;
}
$.endContour = function () {
clearBuff();
firstVertex = true;
}
$.vertex = function (x, y) {
clearBuff();
if (firstVertex) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
firstVertex = false;
}
$.bezierVertex = function (cp1x, cp1y, cp2x, cp2y, x, y) {
clearBuff();
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
$.quadraticVertex = function (cp1x, cp1y, x, y) {
clearBuff();
ctx.quadraticCurveTo(cp1x, cp1y, x, y);
}
$.bezier = function (x1, y1, x2, y2, x3, y3, x4, y4) {
$.beginShape();
$.vertex(x1, y1);
$.bezierVertex(x2, y2, x3, y3, x4, y4);
$.endShape();
}
$.triangle = function (x1, y1, x2, y2, x3, y3) {
$.beginShape();
$.vertex(x1, y1);
$.vertex(x2, y2);
$.vertex(x3, y3);
$.endShape($.CLOSE);
}
$.quad = function (x1, y1, x2, y2, x3, y3, x4, y4) {
$.beginShape();
$.vertex(x1, y1);
$.vertex(x2, y2);
$.vertex(x3, y3);
$.vertex(x4, y4);
$.endShape($.CLOSE);
}
$.endShape = function (close) {
clearBuff();
if (close) {
ctx.closePath();
}
if (!$._style.noFill) ctx.fill();
if (!$._style.noStroke) ctx.stroke();
if ($._style.noFill && $._style.noStroke) { // eh.
ctx.save();
ctx.fillStyle = "none";
ctx.fill();
ctx.restore();
}
}
function catmullRomSpline(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, numPts, alpha) {
//https://en.wikipedia.org/wiki/Centripetal_Catmull–Rom_spline
function catmullromSplineGetT(t, p0x, p0y, p1x, p1y, alpha) {
let a = Math.pow((p1x - p0x), 2.0) + Math.pow((p1y - p0y), 2.0);
let b = Math.pow(a, alpha * 0.5);
return (b + t);
}
let pts = [];
let t0 = 0.0;
let t1 = catmullromSplineGetT(t0, p0x, p0y, p1x, p1y, alpha);
let t2 = catmullromSplineGetT(t1, p1x, p1y, p2x, p2y, alpha);
let t3 = catmullromSplineGetT(t2, p2x, p2y, p3x, p3y, alpha);
for (let i = 0; i < numPts; i++) {
let t = t1 + i / (numPts - 1) * (t2 - t1);
let s = [(t1 - t) / (t1 - t0), (t - t0) / (t1 - t0), (t2 - t) / (t2 - t1), (t - t1) / (t2 - t1),
(t3 - t) / (t3 - t2), (t - t2) / (t3 - t2), (t2 - t) / (t2 - t0), (t - t0) / (t2 - t0),
(t3 - t) / (t3 - t1), (t - t1) / (t3 - t1)];
for (let j = 0; j < s.length; j += 2) {
if (isNaN(s[j])) {
s[j] = 1;
s[j + 1] = 0;
}
if (!isFinite(s[j])) {
if (s[j] > 0) {
s[j] = 1;
s[j + 1] = 0;
} else {
s[j] = 0;
s[j + 1] = 1;
}
}
}
let a1x = p0x * s[0] + p1x * s[1];
let a1y = p0y * s[0] + p1y * s[1];
let a2x = p1x * s[2] + p2x * s[3];
let a2y = p1y * s[2] + p2y * s[3];
let a3x = p2x * s[4] + p3x * s[5];
let a3y = p2y * s[4] + p3y * s[5];
let b1x = a1x * s[6] + a2x * s[7];
let b1y = a1y * s[6] + a2y * s[7];
let b2x = a2x * s[8] + a3x * s[9];
let b2y = a2y * s[8] + a3y * s[9];
let cx = b1x * s[2] + b2x * s[3];
let cy = b1y * s[2] + b2y * s[3];
pts.push([cx, cy]);
}
return pts;
}
$.curveVertex = function (x, y) {
curveBuff.push([x, y]);
if (curveBuff.length < 4) {
return;
}
let p0 = curveBuff[curveBuff.length - 4];
let p1 = curveBuff[curveBuff.length - 3];
let p2 = curveBuff[curveBuff.length - 2];
let p3 = curveBuff[curveBuff.length - 1];
let pts = catmullRomSpline(...p0, ...p1, ...p2, ...p3, $._style.curveDetail,
$._style.curveAlpha,
);
for (let i = 0; i < pts.length; i++) {
if (firstVertex) {
ctx.moveTo(...pts[i]);
} else {
ctx.lineTo(...pts[i]);
}
firstVertex = false;
}
}
$.curve = function (x1, y1, x2, y2, x3, y3, x4, y4) {
$.beginShape();
$.curveVertex(x1, y1);
$.curveVertex(x2, y2);
$.curveVertex(x3, y3);
$.curveVertex(x4, y4);
$.endShape();
}
//================================================================
// DRAWING MATRIX
//================================================================
$.translate = function (x, y) {
ctx.translate(x, y);
}
$.rotate = function (r) {
ctx.rotate(r);
}
$.scale = function (x, y) {
if (y == undefined) {
y = x;
}
ctx.scale(x, y);
}
$.applyMatrix = function (a, b, c, d, e, f) {
ctx.transform(a, b, c, d, e, f);
}
$.shearX = function (ang) {
ctx.transform(1, 0, Math.tan(ang), 1, 0, 0);
}
$.shearY = function (ang) {
ctx.transform(1, Math.tan(ang), 0, 1, 0, 0);
}
$.resetMatrix = function () {
ctx.resetTransform();
ctx.scale($._pixelDensity, $._pixelDensity);
}
$.pushMatrix = $.push = function () {
$._styleCache.push({ ...$._style });
$._style = $._styleCache[$._styleCache.length - 1];
ctx.save();
}
$.popMatrix = $.pop = function () {
if ($._styleCache.length - 1) {
$._styleCache.pop();
$._style = $._styleCache[$._styleCache.length - 1];
ctx.restore();
}
}
//================================================================
// IMAGING
//================================================================
$.image = function (img, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) {
let drawable = img.MAGIC == $.MAGIC ? img.canvas : img;
function reset() {
if (img.MAGIC != $.MAGIC || !$._tint) {
return;
}
let c = img.canvas.getContext('2d');
c.save();
c.resetTransform();
c.clearRect(0, 0, c.canvas.width, c.canvas.height);
c.drawImage(tmpCt2.canvas, 0, 0);
c.restore();
}
if (img.MAGIC == $.MAGIC && $._tint != null) {
makeTmpCt2(img.canvas.width, img.canvas.height);
tmpCt2.drawImage(img.canvas, 0, 0);
img.tinted($._tint);
}
if (!dWidth) {
if (img.MAGIC == $.MAGIC || img.width) {
ctx.drawImage(drawable, dx, dy, img.width, img.height);
} else {
ctx.drawImage(drawable, dx, dy, img.videoWidth, img.videoHeight);
}
reset();
return;
}
if (!sx) {
ctx.drawImage(drawable, dx, dy, dWidth, dHeight);
reset();
return;
}
if (!sWidth) {
sWidth = drawable.width;
}
if (!sHeight) {
sHeight = drawable.height;
}
ctx.drawImage(drawable, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
reset();
}
$.loadPixels = function () {
imgData = ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
$.pixels = imgData.data;
}
$.updatePixels = function () {
if (imgData != null) {
ctx.putImageData(imgData, 0, 0);
}
}
$.loadImage = function (url, callback) {
preloadCnt++;
let g = $.createGraphics(100, 100);
let c = g.canvas.getContext('2d');
let img = new Image();
img.src = url;
img.crossOrigin = "Anonymous";
img.onload = function () {
c.canvas.width = img.width;
c.canvas.height = img.height;
g.width = img.width;
g.height = img.height;
c.drawImage(img, 0, 0);
preloadCnt--;
if (callback) {
callback(g);
}
}
return g;
}
let filterImpl = {};
filterImpl[$.THRESHOLD] = function (data, thresh) {
if (thresh == undefined) {
thresh = 127.5;
} else {
thresh *= 255;
}
for (let i = 0; i < data.length; i += 4) {
const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
data[i] = data[i + 1] = data[i + 2] = (gray >= thresh) ? 255 : 0;
}
}
filterImpl[$.GRAY] = function (data) {
for (let i = 0; i < data.length; i += 4) {
const gray = (0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2]);
data[i] = data[i + 1] = data[i + 2] = gray;
}
}
filterImpl[$.OPAQUE] = function (data) {
for (let i = 0; i < data.length; i += 4) {
data[i + 3] = 255;
}
}
filterImpl[$.INVERT] = function (data) {
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
}
filterImpl[$.POSTERIZE] = function (data, lvl) {
let lvl1 = lvl - 1;
for (let i = 0; i < data.length; i += 4) {
data[i] = ((data[i] * lvl) >> 8) * 255 / lvl1;
data[i + 1] = ((data[i + 1] * lvl) >> 8) * 255 / lvl1;
data[i + 2] = ((data[i + 2] * lvl) >> 8) * 255 / lvl1;
}
}
filterImpl[$.DILATE] = function (data) {
makeTmpBuf();
tmpBuf.set(data);
let [w, h] = [ctx.canvas.width, ctx.canvas.height];
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
let l = 4 * Math.max(j - 1, 0);
let r = 4 * Math.min(j + 1, w - 1);
let t = 4 * Math.max(i - 1, 0) * w;
let b = 4 * Math.min(i + 1, h - 1) * w;
let oi = 4 * i * w;
let oj = 4 * j;
for (let k = 0; k < 4; k++) {
let kt = k + t;
let kb = k + b;
let ko = k + oi;
data[oi + oj + k] = Math.max(
/*tmpBuf[kt+l],*/tmpBuf[kt + oj],/*tmpBuf[kt+r],*/
tmpBuf[ko + l], tmpBuf[ko + oj], tmpBuf[ko + r],
/*tmpBuf[kb+l],*/tmpBuf[kb + oj],/*tmpBuf[kb+r],*/
);
}
}
}
}
filterImpl[$.ERODE] = function (data) {
makeTmpBuf();
tmpBuf.set(data);
let [w, h] = [ctx.canvas.width, ctx.canvas.height];
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
let l = 4 * Math.max(j - 1, 0);
let r = 4 * Math.min(j + 1, w - 1);
let t = 4 * Math.max(i - 1, 0) * w;
let b = 4 * Math.min(i + 1, h - 1) * w;
let oi = 4 * i * w;
let oj = 4 * j;
for (let k = 0; k < 4; k++) {
let kt = k + t;
let kb = k + b;
let ko = k + oi;
data[oi + oj + k] = Math.min(
/*tmpBuf[kt+l],*/tmpBuf[kt + oj],/*tmpBuf[kt+r],*/
tmpBuf[ko + l], tmpBuf[ko + oj], tmpBuf[ko + r],
/*tmpBuf[kb+l],*/tmpBuf[kb + oj],/*tmpBuf[kb+r],*/
);
}
}
}
}
filterImpl[$.BLUR] = function (data, rad) {
rad = rad || 1;
rad = Math.floor(rad * $._pixelDensity);
makeTmpBuf();
tmpBuf.set(data);
let ksize = rad * 2 + 1;
function gauss1d(ksize) {
let im = new Float32Array(ksize);
let sigma = 0.3 * rad + 0.8;
let ss2 = sigma * sigma * 2;
for (let i = 0; i < ksize; i++) {
let x = (i - ksize / 2);
let z = Math.exp(-(x * x) / (ss2)) / (2.5066282746 * sigma);
im[i] = z;
}
return im;
}
let kern = gauss1d(ksize);
let [w, h] = [ctx.canvas.width, ctx.canvas.height];
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
for (let k = 0; k < ksize; k++) {
let jk = Math.min(Math.max(j - rad + k, 0), w - 1);
let idx = 4 * (i * w + jk);
s0 += tmpBuf[idx] * kern[k];
s1 += tmpBuf[idx + 1] * kern[k];
s2 += tmpBuf[idx + 2] * kern[k];
s3 += tmpBuf[idx + 3] * kern[k];
}
let idx = 4 * (i * w + j);
data[idx] = s0;
data[idx + 1] = s1;
data[idx + 2] = s2;
data[idx + 3] = s3;
}
}
tmpBuf.set(data);
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
for (let k = 0; k < ksize; k++) {
let ik = Math.min(Math.max(i - rad + k, 0), h - 1);
let idx = 4 * (ik * w + j);
s0 += tmpBuf[idx] * kern[k];
s1 += tmpBuf[idx + 1] * kern[k];
s2 += tmpBuf[idx + 2] * kern[k];
s3 += tmpBuf[idx + 3] * kern[k];
}
let idx = 4 * (i * w + j);
data[idx] = s0;
data[idx + 1] = s1;
data[idx + 2] = s2;
data[idx + 3] = s3;
}
}
}
filterImpl[$.BRIGHTNESS] = function (data, amount) {
amount = (amount == undefined) ? 1.5 : amount;
for (let i = 0; i < data.length; i += 4) {
data[i] *= amount;
data[i + 1] *= amount;
data[i + 2] *= amount;
}
}
filterImpl[$.CONTRAST] = function (data, amount) {
amount = (amount == undefined) ? 1.5 : amount;
for (let i = 0; i < data.length; i += 4) {
data[i + 0] = ((data[i + 0] / 255 - 0.5) * amount + 0.5) * 255;
data[i + 1] = ((data[i + 1] / 255 - 0.5) * amount + 0.5) * 255;
data[i + 2] = ((data[i + 2] / 255 - 0.5) * amount + 0.5) * 255;
}
}
filterImpl[$.SATURATE] = function (data, amount) {
amount = (amount == undefined) ? 1.5 : amount;
amount = Math.max(0, amount);
const lumR = (1 - amount) * 0.3086;
const lumG = (1 - amount) * 0.6094;
const lumB = (1 - amount) * 0.082;
const shiftW = width << 2;
for (let j = 0; j < height; j++) {
const offset = j * shiftW;
for (let i = 0; i < width; i++) {
const pos = offset + (i << 2);
const r = data[pos + 0];
const g = data[pos + 1];
const b = data[pos + 2];
data[pos + 0] = (lumR + amount) * r + lumG * g + lumB * b;
data[pos + 1] = lumR * r + (lumG + amount) * g + lumB * b;
data[pos + 2] = lumR * r + lumG * g + (lumB + amount) * b;
}
}
}
function makeTmpCtx(w, h) {
if (tmpCtx == null) {
tmpCtx = document.createElement("canvas").getContext('2d');
// document.body.appendChild(tmpCtx.canvas)
}
if (w == undefined) {
w = ctx.canvas.width;
h = ctx.canvas.height;
}
if (tmpCtx.canvas.width != w || tmpCtx.canvas.height != h) {
tmpCtx.canvas.width = w;
tmpCtx.canvas.height = h;
}
}
function makeTmpCt2(w, h) {
if (tmpCt2 == null) {
tmpCt2 = document.createElement("canvas").getContext('2d');
// document.body.appendChild(tmpCt2.canvas)
}
if (w == undefined) {
w = ctx.canvas.width;
h = ctx.canvas.height;
}
if (tmpCt2.canvas.width != w || tmpCt2.canvas.height != h) {
tmpCt2.canvas.width = w;
tmpCt2.canvas.height = h;
}
}
function makeTmpBuf() {
let l = ctx.canvas.width * ctx.canvas.height * 4;
if (tmpBuf == null || l != tmpBuf.length) {
tmpBuf = new Uint8ClampedArray(l);
}
}
function nativeFilter(filtstr) {
tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.filter = filtstr;
tmpCtx.drawImage(ctx.canvas, 0, 0);
ctx.save();
ctx.resetTransform();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(tmpCtx.canvas, 0, 0);
ctx.restore();
}
$.filter = function (typ, x) {
let support = $.HARDWARE_FILTERS && (ctx.filter != undefined);
if (support) {
makeTmpCtx();
if (typ == $.THRESHOLD) {
if (x == undefined) {
x = 0.5;
}
x = Math.max(x, 0.00001);
let b = Math.floor((0.5 / x) * 100);
nativeFilter(`saturate(0%) brightness(${b}%) contrast(1000000%)`);
} else if (typ == $.GRAY) {
nativeFilter(`saturate(0%)`);
} else if (typ == $.OPAQUE) {
tmpCtx.fillStyle = "black";
tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.drawImage(ctx.canvas, 0, 0);
ctx.save();
ctx.resetTransform();
ctx.drawImage(tmpCtx.canvas, 0, 0);
ctx.restore();
} else if (typ == $.INVERT) {
nativeFilter(`invert(100%)`);
} else if (typ == $.BLUR) {
nativeFilter(`blur(${Math.ceil(x * $._pixelDensity / 1) || 1}px)`);
} else if (typ == $.BRIGHTNESS) {
x = (x == undefined) ? 1.5 : x;
let b = Math.max(0, x) * 100;
nativeFilter(`brightness(${b}%)`);
} else if (typ == $.CONTRAST) {
x = (x == undefined) ? 1.5 : x;
let c = Math.max(0, x) * 100;
nativeFilter(`contrast(${c}%)`);
} else if (typ == $.SATURATE) {
x = (x == undefined) ? 1.5 : x;
let s = Math.max(0, x) * 100;
nativeFilter(`saturate(${s}%)`);
} else {
let imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
filterImpl[typ](imgData.data, x);
ctx.putImageData(imgData, 0, 0);
}
} else {
let imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
filterImpl[typ](imgData.data, x);
ctx.putImageData(imgData, 0, 0);
}
}
$.resize = function (w, h) {
makeTmpCtx();
tmpCtx.drawImage(ctx.canvas, 0, 0);
$.width = w;
$.height = h;
ctx.canvas.width = w * $._pixelDensity;
ctx.canvas.height = h * $._pixelDensity;
ctx.save();
ctx.resetTransform();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(tmpCtx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore();
}
$.get = function (x, y, w, h) {
if (x != undefined && w == undefined) {
let c = ctx.getImageData(x, y, 1, 1).data;
return new $.Color(c[0], c[1], c[2], c[3] / 255);
}
x = x || 0;
y = y || 0;
w = w || $.width;
h = h || $.height;
let g = $.createGraphics(w, h);
g.pixelDensity($._pixelDensity);
let imgData = ctx.getImageData(
x * $._pixelDensity, y * $._pixelDensity,
w * $._pixelDensity, h * $._pixelDensity);
g.canvas.getContext('2d').putImageData(imgData, 0, 0);
return g;
}
$.set = function (x, y, c) {
if (c.MAGIC == $.MAGIC) {
let old = $._tint;
$._tint = null;
$.image(c, x, y);
$._tint = old;
return;
}
let idx = 4 * ((y * $._pixelDensity * ctx.canvas.width) + (x * $._pixelDensity));
$.pixels[idx] = c._r;
$.pixels[idx + 1] = c._g;
$.pixels[idx + 2] = c._b;
$.pixels[idx + 3] = c._a * 255;
}
$.tinted = function () {
let col = $.color(...Array.from(arguments));
let alpha = col._a;
col._a = 1;
makeTmpCtx();
tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.fillStyle = col;
tmpCtx.fillRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.globalCompositeOperation = "multiply";
tmpCtx.drawImage(ctx.canvas, 0, 0);
tmpCtx.globalCompositeOperation = "source-over";
ctx.save();
ctx.resetTransform();
let old = ctx.globalCompositeOperation;
ctx.globalCompositeOperation = "source-in";
ctx.drawImage(tmpCtx.canvas, 0, 0);
ctx.globalCompositeOperation = old;
ctx.restore();
tmpCtx.globalAlpha = alpha;
tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.drawImage(ctx.canvas, 0, 0);
tmpCtx.globalAlpha = 1;
ctx.save();
ctx.resetTransform();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(tmpCtx.canvas, 0, 0);
ctx.restore();
}
$.tint = function () {
$._tint = $.color(...Array.from(arguments));
}
$.noTint = function () {
$._tint = null;
}
$.mask = function (img) {
ctx.save();
ctx.resetTransform();
let old = ctx.globalCompositeOperation;
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(img.canvas, 0, 0);
ctx.globalCompositeOperation = old;
ctx.restore();
}
$.clearTemporaryBuffers = function () {
tmpCtx = null;
tmpCt2 = null;
tmpBuf = null;
}
$.save = function (name, ext) {
name = name || "untitled";
ext = ext || "png";
var down = document.createElement('a')
down.innerHTML = "[Download]"
down.addEventListener('click', function () {
this.href = ctx.canvas.toDataURL();
this.download = name + "." + ext;
}, false);
document.body.appendChild(down);
down.click()
document.body.removeChild(down);
}
$.saveCanvas = function (a, b, c) {
if (a.MAGIC == $.MAGIC) {
if (c) {
a.save(b, c);
}
let s = b.split(".")
return a.save(s.slice(0, -1).join("."), s[s.length - 1]);
}
if (b) {
return $.save(a, b);
}
let s = a.split(".")
return $.save(s.slice(0, -1).join("."), s[s.length - 1]);
}
//================================================================
// TYPOGRAPHY
//================================================================
$.loadFont = function (url, success, failure) {
let sp = url.split("/");
let name = sp[sp.length - 1].split(".")[0].replace(" ", "");
let f = new FontFace(name, `url('${url}')`);
f.load().then(
function (font) {
document.fonts.add(font);
if (success) {
success(name);
}
},
function (err) {
if (failure) {
failure(err)
}
});
return name;
}
$.textFont = function (x) {
$._style.textFont = x;
}
$.textSize = function (x) {
$._style.textSize = x;
$._style.textLeading = x;
}
$.textLeading = function (x) {
$._style.textLeading = x;
}
$.textStyle = function (x) {
$._style.textStyle = x;
}
$.textAlign = function (horiz, vert) {
ctx.textAlign = horiz;
if (vert) {
ctx.textBaseline = (vert == $.CENTER) ? "middle" : vert;
}
}
$.text = function (str, x, y, w) {
if (!str) {
return;
}
str = str.toString();
if ($._style.noFill && $._style.noStroke) {
return;
}
ctx.font = `${$._style.textStyle} ${$._style.textSize}px ${$._style.textFont}`;
let lines = str.split("\n");
for (let i = 0; i < lines.length; i++) {
if (!$._style.noFill) { ctx.fillText(lines[i], x, y, w) }
if (!$._style.noStroke) { ctx.strokeText(lines[i], x, y, w) }
y += $._style.textLeading;
}
}
$.textWidth = function (str) {
ctx.font = `${$._style.textStyle} ${$._style.textSize}px ${$._style.textFont}`;
return ctx.measureText(str).width;
}
$.textAscent = function (str) {
ctx.font = `${$._style.textStyle} ${$._style.textSize}px ${$._style.textFont}`;
return ctx.measureText(str).actualBoundingBoxAscent;
}
$.textDescent = function (str) {
ctx.font = `${$._style.textStyle} ${$._style.textSize}px ${$._style.textFont}`;
return ctx.measureText(str).actualBoundingBoxDescent;
}
//================================================================
// RANDOM
//================================================================
//https://github.com/processing/p5.js/blob/1.1.9/src/math/noise.js
var PERLIN_YWRAPB = 4; var PERLIN_YWRAP = 1 << PERLIN_YWRAPB;
var PERLIN_ZWRAPB = 8; var PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB;
var PERLIN_SIZE = 4095;
var perlin_octaves = 4; var perlin_amp_falloff = 0.5;
var scaled_cosine = function (i) { return 0.5 * (1.0 - Math.cos(i * Math.PI)); };
var p_perlin;
$.noise = function (x, y, z) {
y = y || 0; z = z || 0;
if (p_perlin == null) {
p_perlin = new Array(PERLIN_SIZE + 1);
for (var i = 0; i < PERLIN_SIZE + 1; i++) {
p_perlin[i] = Math.random();
}
}
if (x < 0) { x = -x; } if (y < 0) { y = -y; } if (z < 0) { z = -z; }
var xi = Math.floor(x), yi = Math.floor(y), zi = Math.floor(z);
var xf = x - xi; var yf = y - yi; var zf = z - zi;
var rxf, ryf;
var r = 0; var ampl = 0.5;
var n1, n2, n3;
for (var o = 0; o < perlin_octaves; o++) {
var of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB);
rxf = scaled_cosine(xf); ryf = scaled_cosine(yf);
n1 = p_perlin[of & PERLIN_SIZE];
n1 += rxf * (p_perlin[(of + 1) & PERLIN_SIZE] - n1);
n2 = p_perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE];
n2 += rxf * (p_perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2);
n1 += ryf * (n2 -