Skip to content

Commit

Permalink
1.9.5
Browse files Browse the repository at this point in the history
  • Loading branch information
quinton-ashley committed Jan 27, 2024
1 parent 889418b commit 549812a
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 166 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ Thanks in large part to @LingDong-'s design, q5 is well organized, concise, and

Features added by @quinton-ashley:

- `opacity`: set the opacity multiplier for anything subsequently drawn to the canvas. Range between 0 and 1.
- `textCache(true)`: Text image caching is enabled by default. Rotated text is only rendered once, and then cached as an image. This can result in ridiculously high 90x performance boosts for text-heavy sketches. Users don't need to change their code, the `text` function can be used as normal, q5 takes care of everything behind the scenes.
- `loadSound()`: Basic sound support in q5.js, returns a Web Audio object with `setVolume()` and `setLoop()` functions added. Not as powerful as p5.sound, but it's good enough in many cases.
- `ctx`: an alias for `drawingContext`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "quinton-ashley",
"name": "q5",
"version": "1.9.4",
"version": "1.9.5",
"description": "An implementation of the p5.js 2D API that's smaller and faster",
"main": "q5.js",
"scripts": {
Expand Down
333 changes: 169 additions & 164 deletions q5.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ function Q5(scope, parent) {

ctx = $.ctx = $.drawingContext = $.canvas.getContext('2d', opt);
Object.assign($.canvas, opt);
if ($._colorMode == 'rgb') $.colorMode('rgb');
defaultStyle();
ctx.save();
if (scope != 'image') {
Expand All @@ -105,163 +106,6 @@ function Q5(scope, parent) {
if (imgData != null) ctx.putImageData(imgData, 0, 0);
};

let filterImpl = [];
filterImpl[$.THRESHOLD] = (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] = (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] = (data) => {
for (let i = 0; i < data.length; i += 4) {
data[i + 3] = 255;
}
};
filterImpl[$.INVERT] = (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] = (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] = (data) => {
makeTmpBuf();
tmpBuf.set(data);
let [w, h] = [$.canvas.width, $.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] = (data) => {
makeTmpBuf();
tmpBuf.set(data);
let [w, h] = [$.canvas.width, $.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] = (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] = [$.canvas.width, $.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;
}
}
};

function makeTmpCtx(w, h) {
h ??= w || $.canvas.height;
w ??= $.canvas.width;
Expand Down Expand Up @@ -297,6 +141,172 @@ function Q5(scope, parent) {
}
}

function initSoftFilters() {
$._filters = [];
$._filters[$.THRESHOLD] = (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;
}
};
$._filters[$.GRAY] = (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;
}
};
$._filters[$.OPAQUE] = (data) => {
for (let i = 0; i < data.length; i += 4) {
data[i + 3] = 255;
}
};
$._filters[$.INVERT] = (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];
}
};
$._filters[$.POSTERIZE] = (data, lvl) => {
lvl ??= 4;
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;
}
};
$._filters[$.DILATE] = (data) => {
makeTmpBuf();
tmpBuf.set(data);
let [w, h] = [$.canvas.width, $.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],*/
);
}
}
}
};
$._filters[$.ERODE] = (data) => {
makeTmpBuf();
tmpBuf.set(data);
let [w, h] = [$.canvas.width, $.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],*/
);
}
}
}
};
$._filters[$.BLUR] = (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] = [$.canvas.width, $.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;
}
}
};
}

function softFilter(typ, x) {
if (!$._filters) initSoftFilters();
let imgData = ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
$._filters[typ](imgData.data, x);
ctx.putImageData(imgData, 0, 0);
}

function nativeFilter(filtstr) {
tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.filter = filtstr;
Expand All @@ -308,12 +318,6 @@ function Q5(scope, parent) {
ctx.restore();
}

function softFilter(typ, x) {
let imgData = ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
filterImpl[typ](imgData.data, x);
ctx.putImageData(imgData, 0, 0);
}

$.filter = (typ, x) => {
if (!ctx.filter) return softFilter(typ, x);
makeTmpCtx();
Expand Down Expand Up @@ -835,7 +839,8 @@ function Q5(scope, parent) {

// COLOR

$.Color = Q5.ColorRGBA_P3;
if (Q5.supportsHDR) $.Color = Q5.ColorRGBA_P3;
else $.Color = Q5.ColorRGBA;

$.colorMode = (mode) => {
$._colorMode = mode;
Expand Down
2 changes: 1 addition & 1 deletion q5.min.js

Large diffs are not rendered by default.

0 comments on commit 549812a

Please sign in to comment.