diff --git a/external/qcms/qcms.js b/external/qcms/qcms.js index 9cd35a1ec..860ca5542 100644 --- a/external/qcms/qcms.js +++ b/external/qcms/qcms.js @@ -1,5 +1,5 @@ /* 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 * * This function is called directly from JavaScript. * @param {number} transformer * @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 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} src3 * @param {number} src4 - * @param {boolean} css + * @returns {number} */ -export function qcms_convert_four(transformer, src1, src2, src3, src4, css) { - wasm.qcms_convert_four(transformer, src1, src2, src3, src4, css); +export function qcms_convert_four(transformer, src1, src2, src3, src4) { + 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. * @param {number} transformer * @param {number} src - * @param {boolean} css + * @returns {number} */ -export function qcms_convert_one(transformer, src, css) { - wasm.qcms_convert_one(transformer, src, css); +export function qcms_convert_one(transformer, src) { + 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} src2 * @param {number} src3 - * @param {boolean} css + * @returns {number} */ -export function qcms_convert_three(transformer, src1, src2, src3, css) { - wasm.qcms_convert_three(transformer, src1, src2, src3, css); +export function qcms_convert_three(transformer, src1, src2, src3) { + 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() { const import0 = { __proto__: null, - __wbg___wbindgen_throw_6b64449b9b9ed33c: function(arg0, arg1) { + __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }, __wbg_copy_result_0d15f3bf9d9012ae: function(arg0, arg1) { 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() { const table = wasm.__wbindgen_externrefs; const offset = table.grow(4); @@ -135,8 +136,7 @@ function __wbg_get_imports() { } function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return decodeText(ptr, len); + return decodeText(ptr >>> 0, len); } let cachedUint8ArrayMemory0 = null; @@ -170,8 +170,9 @@ function decodeText(ptr, len) { let WASM_VECTOR_LEN = 0; -let wasmModule, wasm; +let wasmModule, wasmInstance, wasm; function __wbg_finalize_init(instance, module) { + wasmInstance = instance; wasm = instance.exports; wasmModule = module; cachedUint8ArrayMemory0 = null; diff --git a/external/qcms/qcms_bg.wasm b/external/qcms/qcms_bg.wasm index fccd641fe..ea1d07bc4 100644 Binary files a/external/qcms/qcms_bg.wasm and b/external/qcms/qcms_bg.wasm differ diff --git a/external/qcms/qcms_utils.js b/external/qcms/qcms_utils.js index 11d35351e..adbbaa832 100644 --- a/external/qcms/qcms_utils.js +++ b/external/qcms/qcms_utils.js @@ -13,22 +13,28 @@ * 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 { static #memoryArray = null; static _memory = null; - static _mustAddAlpha = false; - + // Where the next `qcms_convert_array` result should land. static _destBuffer = null; static _destOffset = 0; - static _destLength = 0; - - static _cssColor = ""; - - static _makeHexColor = null; + // Set when the destination is RGBA and its alpha channel already holds + // something worth keeping, which is the case whenever the image has an + // /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 + // copied wholesale. + static _keepAlpha = false; static get _memoryArray() { const array = this.#memoryArray; @@ -42,43 +48,31 @@ class QCMS { function copy_result(ptr, len) { // 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 - // to the destination buffer without any intermediate copies. - const { _mustAddAlpha, _destBuffer, _destOffset, _destLength, _memoryArray } = - QCMS; - if (len === _destLength) { + // to the destination buffer without any intermediate copies. The wasm side + // has already laid the result out the way the caller asked for it, so this is + // one bulk copy unless the destination's alpha has to survive. + const { _destBuffer, _destOffset, _keepAlpha, _memoryArray } = QCMS; + if (!_keepAlpha) { _destBuffer.set(_memoryArray.subarray(ptr, ptr + len), _destOffset); return; } - if (_mustAddAlpha) { - for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 3, j += 4) { - _destBuffer[j] = _memoryArray[i]; - _destBuffer[j + 1] = _memoryArray[i + 1]; - _destBuffer[j + 2] = _memoryArray[i + 2]; - _destBuffer[j + 3] = 255; - } - } else { - for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 3, j += 4) { - _destBuffer[j] = _memoryArray[i]; - _destBuffer[j + 1] = _memoryArray[i + 1]; - _destBuffer[j + 2] = _memoryArray[i + 2]; + const count = len >> 2; + const destStart = _destBuffer.byteOffset + _destOffset; + if (((destStart | ptr) & 3) === 0) { + // Both sides are pixel-aligned, so RGB can be merged a whole pixel at a + // time. `len` is a multiple of 4 here: the wasm side wrote RGBA. + 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); } + return; + } + for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 4, j += 4) { + _destBuffer[j] = _memoryArray[i]; + _destBuffer[j + 1] = _memoryArray[i + 1]; + _destBuffer[j + 2] = _memoryArray[i + 2]; } } -function copy_rgb(ptr) { - 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 }; +export { copy_result, QCMS }; diff --git a/src/core/icc_colorspace.js b/src/core/icc_colorspace.js index 04ad9fc36..2104033ff 100644 --- a/src/core/icc_colorspace.js +++ b/src/core/icc_colorspace.js @@ -61,30 +61,28 @@ class IccColorSpace extends ColorSpace { switch (numComps) { case 1: inType = DataType.Gray8; - this.#convertPixel = (src, srcOffset, css) => - qcms_convert_one(this.#transformer, src[srcOffset] * 255, css); + this.#convertPixel = (src, srcOffset) => + qcms_convert_one(this.#transformer, src[srcOffset] * 255); break; case 3: inType = DataType.RGB8; - this.#convertPixel = (src, srcOffset, css) => + this.#convertPixel = (src, srcOffset) => qcms_convert_three( this.#transformer, src[srcOffset] * 255, src[srcOffset + 1] * 255, - src[srcOffset + 2] * 255, - css + src[srcOffset + 2] * 255 ); break; case 4: inType = DataType.CMYK; - this.#convertPixel = (src, srcOffset, css) => + this.#convertPixel = (src, srcOffset) => qcms_convert_four( this.#transformer, src[srcOffset] * 255, src[srcOffset + 1] * 255, src[srcOffset + 2] * 255, - src[srcOffset + 3] * 255, - css + src[srcOffset + 3] * 255 ); break; default: @@ -105,16 +103,15 @@ class IccColorSpace extends ColorSpace { } getRgbHex(src, srcOffset) { - this.#convertPixel(src, srcOffset, /* css */ true); - return QCMS._cssColor; + const color = this.#convertPixel(src, srcOffset); + return Util.makeHexColor(color >> 16, (color >> 8) & 0xff, color & 0xff); } getRgbItem(src, srcOffset, dest, destOffset) { - QCMS._destBuffer = dest; - QCMS._destOffset = destOffset; - QCMS._destLength = 3; - this.#convertPixel(src, srcOffset, /* css */ false); - QCMS._destBuffer = null; + const color = this.#convertPixel(src, srcOffset); + dest[destOffset] = color >> 16; + dest[destOffset + 1] = (color >> 8) & 0xff; + dest[destOffset + 2] = color & 0xff; } getRgbItems(src, count, dest, destOffset, alpha01) { @@ -127,8 +124,10 @@ class IccColorSpace extends ColorSpace { } QCMS._destBuffer = dest; QCMS._destOffset = destOffset; - QCMS._destLength = count * (3 + alpha01); - qcms_convert_array(this.#transformer, scaled); + // `scaled` is freshly allocated, so unlike in getRgbBuffer it can never + // 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; } @@ -140,12 +139,12 @@ class IccColorSpace extends ColorSpace { src[i] *= scale; } } - QCMS._mustAddAlpha = alpha01 && dest.buffer === src.buffer; QCMS._destBuffer = dest; QCMS._destOffset = destOffset; - QCMS._destLength = count * (3 + alpha01); - qcms_convert_array(this.#transformer, src); - QCMS._mustAddAlpha = false; + // The wasm side always fills alpha in, so say when the destination's own + // alpha must survive: an /SMask has been decoded into it by now. + QCMS._keepAlpha = alpha01 === 1 && dest.buffer !== src.buffer; + qcms_convert_array(this.#transformer, src, alpha01 === 1); QCMS._destBuffer = null; } @@ -172,7 +171,6 @@ class IccColorSpace extends ColorSpace { }); isUsable = !!this._module; QCMS._memory = this._module.memory; - QCMS._makeHexColor = Util.makeHexColor.bind(Util); } catch (e) { warn(`ICCBased color space: "${e}".`); }