Update qcms wrapper

Few performance improvements have been made in the qcms wrapper, see:
2ae4ee7233
This commit is contained in:
calixteman 2026-07-27 20:33:07 +02:00
parent 1609bd87c5
commit e7acffcef3
No known key found for this signature in database
GPG Key ID: 0C5442631EE0691F
4 changed files with 77 additions and 84 deletions

45
external/qcms/qcms.js vendored
View File

@ -1,5 +1,5 @@
/* THIS FILE IS GENERATED - DO NOT EDIT */ /* THIS FILE IS GENERATED - DO NOT EDIT */
import { copy_result, copy_rgb, make_cssRGB } from './qcms_utils.js'; import { copy_result } from './qcms_utils.js';
/** /**
@ -25,16 +25,20 @@ export const Intent = Object.freeze({
}); });
/** /**
* Converts `src` and hands the result to `copy_result`, laid out as RGB, or as
* RGBA with an opaque alpha when `add_alpha` is set.
*
* # Safety * # Safety
* *
* This function is called directly from JavaScript. * This function is called directly from JavaScript.
* @param {number} transformer * @param {number} transformer
* @param {Uint8Array} src * @param {Uint8Array} src
* @param {boolean} add_alpha
*/ */
export function qcms_convert_array(transformer, src) { export function qcms_convert_array(transformer, src, add_alpha) {
const ptr0 = passArray8ToWasm0(src, wasm.__wbindgen_malloc); const ptr0 = passArray8ToWasm0(src, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN; const len0 = WASM_VECTOR_LEN;
wasm.qcms_convert_array(transformer, ptr0, len0); wasm.qcms_convert_array(transformer, ptr0, len0, add_alpha);
} }
/** /**
@ -46,10 +50,11 @@ export function qcms_convert_array(transformer, src) {
* @param {number} src2 * @param {number} src2
* @param {number} src3 * @param {number} src3
* @param {number} src4 * @param {number} src4
* @param {boolean} css * @returns {number}
*/ */
export function qcms_convert_four(transformer, src1, src2, src3, src4, css) { export function qcms_convert_four(transformer, src1, src2, src3, src4) {
wasm.qcms_convert_four(transformer, src1, src2, src3, src4, css); const ret = wasm.qcms_convert_four(transformer, src1, src2, src3, src4);
return ret >>> 0;
} }
/** /**
@ -58,10 +63,11 @@ export function qcms_convert_four(transformer, src1, src2, src3, src4, css) {
* This function is called directly from JavaScript. * This function is called directly from JavaScript.
* @param {number} transformer * @param {number} transformer
* @param {number} src * @param {number} src
* @param {boolean} css * @returns {number}
*/ */
export function qcms_convert_one(transformer, src, css) { export function qcms_convert_one(transformer, src) {
wasm.qcms_convert_one(transformer, src, css); const ret = wasm.qcms_convert_one(transformer, src);
return ret >>> 0;
} }
/** /**
@ -72,10 +78,11 @@ export function qcms_convert_one(transformer, src, css) {
* @param {number} src1 * @param {number} src1
* @param {number} src2 * @param {number} src2
* @param {number} src3 * @param {number} src3
* @param {boolean} css * @returns {number}
*/ */
export function qcms_convert_three(transformer, src1, src2, src3, css) { export function qcms_convert_three(transformer, src1, src2, src3) {
wasm.qcms_convert_three(transformer, src1, src2, src3, css); const ret = wasm.qcms_convert_three(transformer, src1, src2, src3);
return ret >>> 0;
} }
/** /**
@ -106,18 +113,12 @@ export function qcms_transformer_from_memory(mem, in_type, intent) {
function __wbg_get_imports() { function __wbg_get_imports() {
const import0 = { const import0 = {
__proto__: null, __proto__: null,
__wbg___wbindgen_throw_6b64449b9b9ed33c: function(arg0, arg1) { __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1)); throw new Error(getStringFromWasm0(arg0, arg1));
}, },
__wbg_copy_result_0d15f3bf9d9012ae: function(arg0, arg1) { __wbg_copy_result_0d15f3bf9d9012ae: function(arg0, arg1) {
copy_result(arg0 >>> 0, arg1 >>> 0); copy_result(arg0 >>> 0, arg1 >>> 0);
}, },
__wbg_copy_rgb_0106d9d9464fce43: function(arg0) {
copy_rgb(arg0 >>> 0);
},
__wbg_make_cssRGB_8e24b34f71f5363e: function(arg0) {
make_cssRGB(arg0 >>> 0);
},
__wbindgen_init_externref_table: function() { __wbindgen_init_externref_table: function() {
const table = wasm.__wbindgen_externrefs; const table = wasm.__wbindgen_externrefs;
const offset = table.grow(4); const offset = table.grow(4);
@ -135,8 +136,7 @@ function __wbg_get_imports() {
} }
function getStringFromWasm0(ptr, len) { function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0; return decodeText(ptr >>> 0, len);
return decodeText(ptr, len);
} }
let cachedUint8ArrayMemory0 = null; let cachedUint8ArrayMemory0 = null;
@ -170,8 +170,9 @@ function decodeText(ptr, len) {
let WASM_VECTOR_LEN = 0; let WASM_VECTOR_LEN = 0;
let wasmModule, wasm; let wasmModule, wasmInstance, wasm;
function __wbg_finalize_init(instance, module) { function __wbg_finalize_init(instance, module) {
wasmInstance = instance;
wasm = instance.exports; wasm = instance.exports;
wasmModule = module; wasmModule = module;
cachedUint8ArrayMemory0 = null; cachedUint8ArrayMemory0 = null;

Binary file not shown.

View File

@ -13,22 +13,28 @@
* limitations under the License. * limitations under the License.
*/ */
// Alpha lives in the high byte of a pixel on a little-endian host and in the
// low byte on a big-endian one.
const ALPHA_MASK =
new Uint8Array(new Uint32Array([1]).buffer)[0] === 1 ? 0xff000000 : 0x000000ff;
const RGB_MASK = ~ALPHA_MASK;
class QCMS { class QCMS {
static #memoryArray = null; static #memoryArray = null;
static _memory = null; static _memory = null;
static _mustAddAlpha = false; // Where the next `qcms_convert_array` result should land.
static _destBuffer = null; static _destBuffer = null;
static _destOffset = 0; static _destOffset = 0;
static _destLength = 0; // Set when the destination is RGBA and its alpha channel already holds
// something worth keeping, which is the case whenever the image has an
static _cssColor = ""; // /SMask: `fillOpacity` has run by then. The Wasm side does not know about
// that, so it always fills alpha in, and the bytes are merged here instead of
static _makeHexColor = null; // copied wholesale.
static _keepAlpha = false;
static get _memoryArray() { static get _memoryArray() {
const array = this.#memoryArray; const array = this.#memoryArray;
@ -42,43 +48,31 @@ class QCMS {
function copy_result(ptr, len) { function copy_result(ptr, len) {
// This function is called from the wasm module (it's an external // This function is called from the wasm module (it's an external
// "C" function). Its goal is to copy the result from the wasm memory // "C" function). Its goal is to copy the result from the wasm memory
// to the destination buffer without any intermediate copies. // to the destination buffer without any intermediate copies. The wasm side
const { _mustAddAlpha, _destBuffer, _destOffset, _destLength, _memoryArray } = // has already laid the result out the way the caller asked for it, so this is
QCMS; // one bulk copy unless the destination's alpha has to survive.
if (len === _destLength) { const { _destBuffer, _destOffset, _keepAlpha, _memoryArray } = QCMS;
if (!_keepAlpha) {
_destBuffer.set(_memoryArray.subarray(ptr, ptr + len), _destOffset); _destBuffer.set(_memoryArray.subarray(ptr, ptr + len), _destOffset);
return; return;
} }
if (_mustAddAlpha) { const count = len >> 2;
for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 3, j += 4) { const destStart = _destBuffer.byteOffset + _destOffset;
_destBuffer[j] = _memoryArray[i]; if (((destStart | ptr) & 3) === 0) {
_destBuffer[j + 1] = _memoryArray[i + 1]; // Both sides are pixel-aligned, so RGB can be merged a whole pixel at a
_destBuffer[j + 2] = _memoryArray[i + 2]; // time. `len` is a multiple of 4 here: the wasm side wrote RGBA.
_destBuffer[j + 3] = 255; const dest32 = new Uint32Array(_destBuffer.buffer, destStart, count);
const src32 = new Uint32Array(QCMS._memory.buffer, ptr, count);
for (let i = 0; i < count; i++) {
dest32[i] = (dest32[i] & ALPHA_MASK) | (src32[i] & RGB_MASK);
} }
} else { return;
for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 3, j += 4) { }
for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 4, j += 4) {
_destBuffer[j] = _memoryArray[i]; _destBuffer[j] = _memoryArray[i];
_destBuffer[j + 1] = _memoryArray[i + 1]; _destBuffer[j + 1] = _memoryArray[i + 1];
_destBuffer[j + 2] = _memoryArray[i + 2]; _destBuffer[j + 2] = _memoryArray[i + 2];
} }
} }
}
function copy_rgb(ptr) { export { copy_result, QCMS };
const { _destBuffer, _destOffset, _memoryArray } = QCMS;
_destBuffer[_destOffset] = _memoryArray[ptr];
_destBuffer[_destOffset + 1] = _memoryArray[ptr + 1];
_destBuffer[_destOffset + 2] = _memoryArray[ptr + 2];
}
function make_cssRGB(ptr) {
const { _memoryArray } = QCMS;
QCMS._cssColor = QCMS._makeHexColor(
_memoryArray[ptr],
_memoryArray[ptr + 1],
_memoryArray[ptr + 2]
);
}
export { copy_result, copy_rgb, make_cssRGB, QCMS };

View File

@ -61,30 +61,28 @@ class IccColorSpace extends ColorSpace {
switch (numComps) { switch (numComps) {
case 1: case 1:
inType = DataType.Gray8; inType = DataType.Gray8;
this.#convertPixel = (src, srcOffset, css) => this.#convertPixel = (src, srcOffset) =>
qcms_convert_one(this.#transformer, src[srcOffset] * 255, css); qcms_convert_one(this.#transformer, src[srcOffset] * 255);
break; break;
case 3: case 3:
inType = DataType.RGB8; inType = DataType.RGB8;
this.#convertPixel = (src, srcOffset, css) => this.#convertPixel = (src, srcOffset) =>
qcms_convert_three( qcms_convert_three(
this.#transformer, this.#transformer,
src[srcOffset] * 255, src[srcOffset] * 255,
src[srcOffset + 1] * 255, src[srcOffset + 1] * 255,
src[srcOffset + 2] * 255, src[srcOffset + 2] * 255
css
); );
break; break;
case 4: case 4:
inType = DataType.CMYK; inType = DataType.CMYK;
this.#convertPixel = (src, srcOffset, css) => this.#convertPixel = (src, srcOffset) =>
qcms_convert_four( qcms_convert_four(
this.#transformer, this.#transformer,
src[srcOffset] * 255, src[srcOffset] * 255,
src[srcOffset + 1] * 255, src[srcOffset + 1] * 255,
src[srcOffset + 2] * 255, src[srcOffset + 2] * 255,
src[srcOffset + 3] * 255, src[srcOffset + 3] * 255
css
); );
break; break;
default: default:
@ -105,16 +103,15 @@ class IccColorSpace extends ColorSpace {
} }
getRgbHex(src, srcOffset) { getRgbHex(src, srcOffset) {
this.#convertPixel(src, srcOffset, /* css */ true); const color = this.#convertPixel(src, srcOffset);
return QCMS._cssColor; return Util.makeHexColor(color >> 16, (color >> 8) & 0xff, color & 0xff);
} }
getRgbItem(src, srcOffset, dest, destOffset) { getRgbItem(src, srcOffset, dest, destOffset) {
QCMS._destBuffer = dest; const color = this.#convertPixel(src, srcOffset);
QCMS._destOffset = destOffset; dest[destOffset] = color >> 16;
QCMS._destLength = 3; dest[destOffset + 1] = (color >> 8) & 0xff;
this.#convertPixel(src, srcOffset, /* css */ false); dest[destOffset + 2] = color & 0xff;
QCMS._destBuffer = null;
} }
getRgbItems(src, count, dest, destOffset, alpha01) { getRgbItems(src, count, dest, destOffset, alpha01) {
@ -127,8 +124,10 @@ class IccColorSpace extends ColorSpace {
} }
QCMS._destBuffer = dest; QCMS._destBuffer = dest;
QCMS._destOffset = destOffset; QCMS._destOffset = destOffset;
QCMS._destLength = count * (3 + alpha01); // `scaled` is freshly allocated, so unlike in getRgbBuffer it can never
qcms_convert_array(this.#transformer, scaled); // alias `dest`: an RGBA destination always has an alpha channel to keep.
QCMS._keepAlpha = alpha01 === 1;
qcms_convert_array(this.#transformer, scaled, alpha01 === 1);
QCMS._destBuffer = null; QCMS._destBuffer = null;
} }
@ -140,12 +139,12 @@ class IccColorSpace extends ColorSpace {
src[i] *= scale; src[i] *= scale;
} }
} }
QCMS._mustAddAlpha = alpha01 && dest.buffer === src.buffer;
QCMS._destBuffer = dest; QCMS._destBuffer = dest;
QCMS._destOffset = destOffset; QCMS._destOffset = destOffset;
QCMS._destLength = count * (3 + alpha01); // The wasm side always fills alpha in, so say when the destination's own
qcms_convert_array(this.#transformer, src); // alpha must survive: an /SMask has been decoded into it by now.
QCMS._mustAddAlpha = false; QCMS._keepAlpha = alpha01 === 1 && dest.buffer !== src.buffer;
qcms_convert_array(this.#transformer, src, alpha01 === 1);
QCMS._destBuffer = null; QCMS._destBuffer = null;
} }
@ -172,7 +171,6 @@ class IccColorSpace extends ColorSpace {
}); });
isUsable = !!this._module; isUsable = !!this._module;
QCMS._memory = this._module.memory; QCMS._memory = this._module.memory;
QCMS._makeHexColor = Util.makeHexColor.bind(Util);
} catch (e) { } catch (e) {
warn(`ICCBased color space: "${e}".`); warn(`ICCBased color space: "${e}".`);
} }