rapier-jxl 1.1.21 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Entropy search shared by the JPEG and photograph doors; coefficients and quantisation never change here.
3
+ import {varDCTSteps} from './vardct.mjs';
4
+ import {ZIGZAG} from './jfif.mjs';
5
+ import {complete} from './bits.mjs';
6
+
7
+ // Frequently nonzero positions first, counted in buckets of eight blocks. Ties keep the natural transposed
8
+ // zigzag, and DC stays first. Integer counts and an explicit tie rule make the permutation deterministic.
9
+ export function coefficientOrders(components) { return complete(coefficientOrderSteps(components)); }
10
+
11
+ export function* coefficientOrderSteps(components) {
12
+ const scan = ZIGZAG.map(n => ((n & 7) << 3) | (n >> 3));
13
+ const histograms = components.map(() => new Uint32Array(64)), total = components.reduce((n, c) => n + c.coeffs.length, 0);
14
+ let done = 0;
15
+ for (let c = 0; c < components.length; c++) {
16
+ const {coeffs} = components[c], counts = histograms[c];
17
+ // At most one 256-pixel group's coefficient count between yields, including JPEG padding blocks.
18
+ for (let from = 0; from < coeffs.length; from += 65536) {
19
+ const end = Math.min(from + 65536, coeffs.length);
20
+ for (let at = from; at < end; at += 64) for (let k = 1; k < 64; k++) if (coeffs[at + scan[k]]) counts[k]++;
21
+ done += end - from;
22
+ if (yield done / total) return null;
23
+ }
24
+ }
25
+ return histograms.map(counts => [0, ...Array.from({length: 63}, (_, k) => k + 1).sort((a, b) => Math.floor(counts[b] / 8) - Math.floor(counts[a] / 8) || a - b)]);
26
+ }
27
+
28
+ // Rung 3 tries a 32-cluster budget at cost 160; rung 4 also tries one learned order. Complete earlier streams
29
+ // remain available on a tie, hurry, memory failure or candidate above the stream limit. No input is mutated.
30
+ // A supplied fallback is already written; it also makes the first writer interruptible for a lossy candidate.
31
+ export function* coefficientEffortSteps(jpeg, effort, fallback) {
32
+ if (effort < 3 && !fallback) return yield* varDCTSteps(jpeg);
33
+ let best = fallback;
34
+ try {
35
+ const first = varDCTSteps(jpeg); let step, hurry;
36
+ while (!(step = first.next()).done) {
37
+ hurry = yield step.value / 2;
38
+ if (hurry && best && step.value < 1) { first.return(); return best; }
39
+ }
40
+ if (!best || step.value.length < best.length) best = step.value;
41
+ if (hurry || effort < 3) return best;
42
+ const parts = effort < 4 ? 1 : 3, clustered = varDCTSteps(jpeg, {clusters: {maxClusters: 32, newClusterCost: 160}});
43
+ while (!(step = clustered.next()).done) {
44
+ hurry = yield 0.5 + step.value / (2 * parts);
45
+ // The final group has already been written. Finish its stream before honoring a late hurry.
46
+ if (hurry && step.value < 1) { clustered.return(); return best; }
47
+ }
48
+ if (step.value.length < best.length) best = step.value;
49
+ if (hurry || effort < 4) return best;
50
+ const learning = coefficientOrderSteps(jpeg.components);
51
+ while (!(step = learning.next()).done) if (yield 0.5 + (1 + step.value) / 6) { learning.return(); return best; }
52
+ const candidate = varDCTSteps(jpeg, {orders: step.value});
53
+ while (!(step = candidate.next()).done) {
54
+ hurry = yield 0.5 + (2 + step.value) / 6;
55
+ if (hurry && step.value < 1) { candidate.return(); return best; }
56
+ }
57
+ return step.value.length < best.length ? step.value : best;
58
+ } catch (error) {
59
+ if (!best || !(error instanceof RangeError && !error.code) && error.code !== 'JXL_SIZE') throw error;
60
+ return best;
61
+ }
62
+ }
@@ -0,0 +1,10 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Only doors with a search read effort; keeping it here adds no work or bytes to the pixel core.
3
+ import {fault} from './admit.mjs';
4
+
5
+ export function admitEffort(options = {}) {
6
+ if (options === null || typeof options !== 'object') throw fault('JXL_INPUT', 'Options are an object.');
7
+ const effort = options.effort === undefined ? 1 : options.effort;
8
+ if (!Number.isInteger(effort) || effort < 1 || effort > 9) throw fault('JXL_INPUT', 'Effort is a whole number from 1 to 9.');
9
+ return effort;
10
+ }
package/effort.d.mts ADDED
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import type {Pixels, EncodeOptions, Job} from './index.mjs';
3
+ export {LIMITS} from './index.mjs';
4
+ export type {Pixels, Limits, EncodeOptions, ErrorCode, EncoderError, Job} from './index.mjs';
5
+ /** The core's encode with a search above it for exact pictures: effort 1 (the default) writes the core's bytes; 2 tries
6
+ * the weighted predictor on every channel, 3 splits its contexts by that predictor's error, 4 models palette indices
7
+ * per group, and 6 also learns gradient-property splits. A level without another rung uses the preceding one.
8
+ * Never larger than the effort below; slower. */
9
+ export function encode(data: Pixels, width: number, height: number, options?: EncodeOptions): Uint8Array;
10
+ /** encode's work and bytes as steps. Above effort 1 an exact picture's first half (fractions up to 0.5) writes effort
11
+ * 1's stream and the second searches; `hurry` ends the search with the smallest stream written so far. */
12
+ export function encodeSteps(data: Pixels, width: number, height: number, options?: EncodeOptions): Job;
package/effort.min.mjs ADDED
@@ -0,0 +1,2 @@
1
+ /*! Rapier JXL 2.1.0 | MIT License Copyright (c) 2026 rapier.website Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
2
+ const t=Object.freeze({bytes:16777216,pixels:24e6,edge:16384}),n=[1];for(let t=1;t<=32;t++)n.push(2*n[t-1]);class e{constructor(t=4096){this.bytes=new Uint8Array(t),this.at=0,this.t=0,this.pending=0}write(t,e){if(!(e>=0&&e<n[t]))throw new Error("bit write out of range: "+t+" bits, "+e);let o=this.t+e*(1<<this.pending),r=this.pending+t;this.at+5>=this.bytes.length&&this.grow();const s=this.bytes;for(;r>=8;)s[this.at++]=255&o,o=Math.floor(o/256),r-=8;this.t=o,this.pending=r}o(t,e){for(let o=0;o<4;o++){const[r,s]=t[o];if(e>=s&&e-s<n[r])return this.write(2,o),void(r&&this.write(r,e-s))}throw new Error("U32 value out of range: "+e)}i(){this.pending&&(this.at+1>=this.bytes.length&&this.grow(),this.bytes[this.at++]=this.t,this.t=0,this.pending=0)}get u(){return 8*this.at+this.pending}grow(n=0){const e=t.bytes+64;if(this.at+n+16>e)throw Object.assign(new Error("The encoded picture exceeds 16 MiB."),{code:"JXL_SIZE"});const o=new Uint8Array(Math.min(e,Math.max(2*this.bytes.length,this.at+n+16)));o.set(this.bytes.subarray(0,this.at)),this.bytes=o}append(t){if(this.at+t.at+8>=this.bytes.length&&this.grow(t.at+8),this.pending)for(let n=0;n<t.at;n++)this.write(8,t.bytes[n]);else this.bytes.set(t.bytes.subarray(0,t.at),this.at),this.at+=t.at;t.pending&&this.write(t.pending,t.t)}finish(){const t=this.at+(this.pending?1:0),n=this.bytes.slice(0,t);return this.pending&&(n[this.at]=this.t),n}}function*o(t,n,e){let o,r;for(;!(o=t.next(r)).done;)r=yield(n+o.value)/e;return o.value}function r(t){return t>=0?2*t:2*-t-1}function s(t){return 31-Math.clz32(t)}function c(t){return t<=1?0:32-Math.clz32(t-1)}const i=(t,n)=>Object.assign(new Error(n),{code:t});function l(n){if(n>t.bytes)throw i("JXL_SIZE","The encoded picture exceeds 16 MiB.")}function f(t){try{return t()}catch(t){if(t instanceof RangeError&&!t.code)throw i("JXL_MEMORY","Not enough memory for this picture.");throw t}}const a=256;function u(t,n){t.o([[9,1],[13,1],[18,1],[30,1]],n)}function h(t,n,e,o,r,{xyb:s=!1,orientation:c=1,colorSpace:i}={}){t.write(16,2815),t.write(1,0),u(t,e),t.write(3,0),u(t,n),t.write(1,0);const l=1!==c;t.write(1,l?1:0),l&&t.write(6,c-1),t.write(4,8),r?t.write(3,5):t.write(2,0),t.write(1,s?1:0),3===o&&"display-p3"===i?t.write(21,903568):3===o?t.write(1,1):t.write(15,14100),l&&t.write(1,1),t.write(2,0),t.write(1,1),t.i()}function y(t,{alpha:n,shift:e=1}){t.write(9,8),n&&t.write(2,0),t.write(2,e),function(t,n){t.write(n?7:5,0),t.write(11,1)}(t,n)}function w(t,n,e=256){const o=Math.ceil(t/e),r=Math.ceil(n/e);return{p:o,M:r,A:Math.ceil(t/(8*e)),m:Math.ceil(n/(8*e)),U:1===o&&1===r}}function p(t,n){const e=n.map(t=>t.length);!function(t,n){t.write(1,0),t.i();for(const e of n)t.o([[10,0],[14,1024],[22,17408],[30,4211712]],e);t.i()}(t,e);const o=t.finish(),r=o.length+e.reduce((t,n)=>t+n,0);l(r);const s=new Uint8Array(r);s.set(o);let c=o.length;for(const t of n)s.set(t,c),c+=t.length;return s}const d=[1,2,3,4,0,5,17,6,16,7,8,9,10,11,12,13,14,15],M=[2,4,3,2,2,4],A=[0,7,3,2,1,15];function g(t,n){const e=new Uint8Array(t.length),o=[];for(let n=0;n<t.length;n++)t[n]>0&&o.push(n);if(!o.length)return e;if(1===o.length)return e[o[0]]=1,e;for(let r=1;;r*=2){const s=o.map(n=>({w:Math.max(t[n],r),I:n,l:null,r:null})).sort((t,n)=>t.w-n.w||t.I-n.I),c=[];let i=0,l=0;const f=()=>i<s.length&&(l>=c.length||s[i].w<=c[l].w)?s[i++]:c[l++];for(;s.length-i+(c.length-l)>1;){const t=f(),n=f();c.push({w:t.w+n.w,I:-1,l:t,r:n})}const a=[[f(),0]];let u=0;for(;a.length;){const[t,n]=a.pop();t.I>=0?(e[t.I]=n,n>u&&(u=n)):a.push([t.l,n+1],[t.r,n+1])}if(u<=n)return e}}function m(t,n,e,o,r,s){const c=[];for(;c.push(o&r),o>>=s;)o--;for(let o=c.length-1;o>=0;o--)t.push(e),n.push(c[o])}function b(t,n){let e=0;for(let o=0;o<n;o++)e=e<<1|1&t,t>>=1;return e}function U(t){const n=new Uint32Array(17),e=new Uint32Array(17),o=new Uint16Array(t.length);for(const e of t)n[e]++;n[0]=0;for(let t=1,o=0;t<=16;t++)o=o+n[t-1]<<1,e[t]=o;for(let n=0;n<t.length;n++)t[n]&&(o[n]=b(e[t[n]]++,t[n]));return o}function I(t){const n=[];for(let e=0;e<t.length;e++)t[e]>0&&n.push(e);const e=n.length,o=n[e-1]+1||1,r=e<=4?new Uint8Array(o):g(t,15).subarray(0,o);let s=0;if(e<=4){n.sort((n,e)=>t[e]-t[n]||n-e),s=4===e&&t[n[0]]>t[n[2]]+t[n[3]]?1:0;const o=e<2?[0]:2===e?[1,1]:3===e?[1,2,2]:s?[1,2,3,3]:[2,2,2,2];n.forEach((t,n)=>{r[t]=o[n]}),n.sort((t,n)=>r[t]-r[n]||t-n)}return{S:o,C:r,T:U(r),v:e&&e<=4?n:null,q:s}}function S(t,n){if(n.S<=1)return;if(n.v){const e=s(n.S-1)+1;t.write(2,1),t.write(2,n.v.length-1);for(const o of n.v)t.write(e,o);return void(4===n.v.length&&t.write(1,n.q))}const e=n.C,o=[],r=[];let c=e.length-1;for(;c>0&&!e[c];)c--;let i=8;for(let t=0;t<=c;){const n=e[t];let s=1;for(;t+s<=c&&e[t+s]===n;)s++;let l=s;if(n&&n!==i&&(o.push(n),r.push(0),l--,i=n),l<3)for(let t=0;t<l;t++)o.push(n),r.push(0);else m(o,r,n?16:17,l-3,n?3:7,n?2:3);t+=s}const l=new Uint32Array(18);for(const t of o)l[t]++;let f=0;for(const t of l)t&&f++;1===f&&(l[o[0]?0:1]=l[o[0]]);const a=g(l,5),u=U(a);let h=18;for(;h>0&&!a[d[h-1]];)h--;t.write(2,0);for(let n=0;n<h;n++){const e=a[d[n]];t.write(M[e],A[e])}for(let n=0;n<o.length;n++){const e=o[n];t.write(a[e],u[e]),16===e?t.write(2,r[n]):17===e&&t.write(3,r[n])}}function x(t,n=0,e=0){return{split:t,L:n,X:e,J:1<<t}}function C(t,n,e=15){t.write(c(e+1),n.split),n.split!==e&&(t.write(c(n.split+1),n.L),t.write(c(n.split-n.L+1),n.X))}function T(t,n,e){if(n<t.J)return e[0]=n,e[1]=0,void(e[2]=0);const o=s(n),r=n-(1<<o),c=o-t.L-t.X;e[0]=t.J+(o-t.split<<t.L+t.X|r>>o-t.L<<t.X|r&(1<<t.X)-1),e[1]=c,e[2]=n>>t.X&(1<<c)-1}function v(t,n,e,o=0){T(t,n,E),e[o+E[0]]++}const E=[0,0,0];function q(t,n,e,o,r=0){T(e,o,E);const s=r+E[0];t.write(n.C[s],n.T[s]),E[1]&&t.write(E[1],E[2])}function L(t,{N:n=null,_:e,P:o}){t.write(1,n?1:0),n&&(t.o([[0,224],[0,512],[0,4096],[15,8]],n.G),t.o([[0,3],[0,4],[2,5],[8,9]],n.minLength),C(t,n.O,8)),e.length>1&&function(t,n){let e=0;for(const t of n)e=Math.max(e,t);const o=e?c(e+1):0;if(o<=3){if(t.write(1,1),t.write(2,o),o)for(const e of n)t.write(o,e);return}t.write(1,0),t.write(1,0);const r=x(8),s=x(4),i=n.length>=64,l=new Uint32Array(i?257:e+1),f=[];for(let t=0;t<n.length;){let e=1;for(;i&&t+e<n.length&&n[t+e]===n[t];)e++;if(e>=8)f.push([n[t],e-1]),l[n[t]]++,v(s,e-1-7,l,224),t+=e;else{for(let o=0;o<e;o++)f.push([n[t],0]),l[n[t]]++;t+=e}}const a=I(l);if(!i){L(t,{_:new Uint8Array(1),P:[{config:r,code:a}]});for(const[n]of f)q(t,a,r,n);return}const u=new Uint32Array(1);u[0]=1,L(t,{N:{G:224,minLength:7,O:s},_:new Uint8Array([1,0]),P:[{config:x(0),code:I(u)},{config:r,code:a}]});for(const[n,e]of f)q(t,a,r,n),e&&q(t,a,s,e-7,224)}(t,e),t.write(1,1);for(const n of o)C(t,n.config,15);for(const n of o){const e=n.code.S-1;if(e){const n=s(e);t.write(1,1),t.write(4,n),t.write(n,e-(1<<n))}else t.write(1,0)}for(const n of o)S(t,n.code)}const X=3,J=5,N=x(4),_=Object.freeze({G:224,minLength:7,O:N}),P=x(0),z=257;function G(t,n=0,e=1){return{R:t,offset:n,j:e,context:-1}}function O(t,n,e,o){return{Z:t,B:n,left:e,right:o}}function R(t,n){const e=(o,r)=>{if(o===r)return n?t[o].k:t[o];const s=o+r>>1;return O(n,n?t[s].Y:s,e(s+1,r),e(o,s))};return e(0,t.length-1)}function j(t){return R(t,0)}function Z(t,n){const e=[n],o=[],s=[];for(const t of e){if(t.left){o.push(t.Z+1,r(t.B)),e.push(t.left,t.right);continue}t.context=s.length,s.push(t);const n=1===t.j?0:31-Math.clz32(t.j&-t.j);o.push(0,t.R,r(t.offset),n,(t.j>>n)-1)}const c=new Uint32Array(64);for(const t of o)v(P,t,c);const i=I(c);L(t,{_:new Uint8Array(6),P:[{config:P,code:i}]});for(const n of o)q(t,i,P,n);return s}function B(t,n){const e=[[3,0],[6,8],[10,72],[13,1096]];if("rct"===n.type)t.write(2,0),t.o(e,n.D),t.o([[0,6],[2,0],[4,2],[6,10]],n.F??6);else if("palette"===n.type)t.write(2,1),t.o(e,n.D),t.o([[0,1],[0,3],[0,4],[13,1]],n.H),t.o([[8,0],[10,256],[12,1280],[16,5376]],n.K),t.o([[0,0],[8,1],[10,257],[16,1281]],n.V??0),t.write(4,n.R??0);else{if("squeeze"!==n.type)throw new Error("unknown transform "+n.type);{t.write(2,2);const o=n.W??[];t.o([[0,0],[4,1],[6,9],[8,41]],o.length);for(const n of o)t.write(1,n.$?1:0),t.write(1,n.tt?1:0),t.o(e,n.D),t.o([[0,1],[0,2],[0,3],[4,4]],n.H)}}}function k(t,{nt:n=!0,et:e=[]}={}){t.write(1,n?1:0),t.write(1,1),t.o([[0,0],[0,1],[4,2],[8,18]],e.length);for(const n of e)B(t,n)}function Y(t,n,e,o=t=>t.context){const r=new Uint8Array(n.length+1);for(const t of n)r[t.context]=o(t)+1;const s=new Uint32Array(2);s[1]=1;const c=[{config:P,code:I(s)}],i=Math.max(...r);for(let t=0;t<i;t++)c.push({config:P,code:I(e[t])});return L(t,{N:_,_:r,P:c}),c}function D(t,n,e,o,s,c){const i=c.R,l=c.offset,f=c.j,a=P,u=N;let h=0;const y=(e,o=a,r=0)=>{t?q(t,n,o,e,r):v(o,e,n,r)},w=()=>{if(h){if(h>=8)y(0),y(h-7-1,u,224);else for(let t=0;t<h;t++)y(0);h=0}},p=(t,n)=>{const o=e[t];let s;1===f?s=o-n-l:(s=Math.round((o-n-l)/f),e[t]=n+l+s*f),0!==s?(w(),y(r(s))):h++};for(let t=0,n=0;t<s;t++)for(let r=0;r<o;r++,n++){let s;if(0===i)s=0;else{const c=r?e[n-1]:t?e[n-o]:0,l=t?e[n-o]:c;if(1===i)s=c;else if(2===i)s=l;else if(3===i)s=(c+l)/2|0;else{const f=c+l-(r&&t?e[n-o-1]:c);if(5===i){const t=c<l?c:l,n=c<l?l:c;s=f<t?t:f>n?n:f}else{if(4!==i)throw new Error("predictor "+i+" is not coded here");s=Math.abs(f-c)<Math.abs(f-l)?c:l}}}p(n,s)}w()}function F(t,n,e,{palette:o=!0}={}){const r=n*e;let s=!0,c=!0;for(let n=0,e=0;e<r&&(255===t[n+3]||(s=!1,c))&&(t[n]===t[n+1]&&t[n]===t[n+2]||(c=!1,s));e++,n+=4);const i=c?1:3,l=!s,f=i+(l?1:0),a=n=>c?l?t[n]+256*t[n+3]:t[n]:t[n]+256*t[n+1]+65536*t[n+2]+(l?16777216*t[n+3]:0),u=new Map;let h=null;for(let t=0,n=0;o&&n<r;n++,t+=4){const n=a(t);if(0!==n&&!u.has(n)&&(u.set(n,1),u.size>=2048))break}if(o&&u.size<2048){const t=[...u.keys()],n=(t,n)=>t>>>8*n&255,e=t=>(.299*n(t,0)+.587*n(t,1)+.114*n(t,2)+.01)*(4===f?n(t,3):1);t.sort((t,n)=>e(t)-e(n));const o=new Map([[0,0]]);t.forEach((t,n)=>o.set(t,n+1)),h={colours:[0,...t],index:o,key:a,byte:n}}return{colour:i,alpha:l,channels:f,palette:h}}function*Q(t,n,e,{shape:r=F(t,n,e),colorSpace:s}={}){let c,i;const l=r.palette?[r,{...r,palette:null}]:[r];for(let r=0;r<l.length;r++)try{const i=yield*o(H(t,n,e,l[r],s),r,l.length);(!c||i.length<c.length)&&(c=i)}catch(t){if("JXL_SIZE"!==t.code)throw t;i=t}if(!c)throw i;return c}function*H(t,n,o,r,s){const{channels:c,palette:i}=r,l=w(n,o),f=i?2:c,u=Array.from({length:f},()=>G(J)),d=j(u),M=Array.from({length:f},()=>new Int16Array(65536)),A=(e,o,r,s)=>{for(let l=0;l<s;l++){let s=4*((o+l)*n+e),f=l*r;for(let n=0;n<r;n++,s+=4,f++)if(i)M[0][f]=i.index.get(i.key(s));else if(c>=3){const n=t[s],e=t[s+1],o=t[s+2],r=n-o,i=o+(r>>1),l=e-i;M[0][f]=i+(l>>1),M[1][f]=r,M[2][f]=l,4===c&&(M[3][f]=t[s+3])}else M[0][f]=t[s],2===c&&(M[1][f]=t[s+3])}};let g=null;if(i){const t=i.colours.length;g=new Int16Array(t*c),i.colours.forEach((n,e)=>{for(let o=0;o<c;o++)g[o*t+e]=i.byte(n,o)})}const m=t=>u[i?l.U?1:0:t],b=i?1:c,U=l.p*l.M,x=2*U,C=(t,e)=>{const r=t%l.p*a,s=(t/l.p|0)*a,c=Math.min(a,n-r),i=Math.min(a,o-s);A(r,s,c,i),e(c,i)};let T=0;if(!i){const t=t=>{const n=I(t),o=new e(128);S(o,n);let r=o.u;for(let e=0;e<t.length;e++)t[e]&&(r+=t[e]*(n.C[e]+Math.max(0,e<224?e-1:e-236)));return r},r=Math.min(n,32),s=Math.min(o,32),i=[J,X].map(t=>({R:t,ot:u.map(()=>new Uint32Array(z))}));for(const t of[0,.5,1]){A(Math.floor((n-r)*t),Math.floor((o-s)*t),r,s);for(const t of i)for(let n=0;n<c;n++)D(null,t.ot[n],M[n],r,s,G(t.R))}u.forEach((n,e)=>{const o=i.map(n=>t(n.ot[e])),r=o[1]<o[0]?1:0;n.R=i[r].R})}const v=i?[{type:"palette",D:0,H:c,K:i.colours.length}]:c>=3?[{type:"rct",D:0,F:6}]:[],E=u.map(()=>new Uint32Array(z)),q=t=>E[u.indexOf(t)];g&&D(null,q(u[0]),g,i.colours.length,c,u[0]);for(let t=0;t<U;t++)C(t,(t,n)=>{for(let e=0;e<b;e++)D(null,q(m(e)),M[e],t,n,m(e))}),yield++T/x;const L=new e(256);h(L,n,o,r.colour,r.alpha,{colorSpace:s}),y(L,{alpha:r.alpha});const N=new e(4096);N.write(1,1),N.write(1,1);const _=Z(N,d),P=Y(N,_,E,t=>u.indexOf(t)),O=t=>P[u.indexOf(t)+1].code;k(N,{nt:!0,et:v}),g&&D(N,O(u[0]),g,i.colours.length,c,u[0]);const R=[];if(l.U)C(0,(t,n)=>{for(let e=0;e<b;e++)D(N,O(m(e)),M[e],t,n,m(e))}),yield 1,R.push(N.finish());else{R.push(N.finish());for(let t=0;t<l.A*l.m+1;t++)R.push(new Uint8Array(0));for(let t=0;t<U;t++)C(t,(t,n)=>{const o=new e(t*n*b+64);k(o,{nt:!0,et:[]});for(let e=0;e<b;e++)D(o,O(m(e)),M[e],t,n,m(e));R.push(o.finish())}),yield++T/x}return p(L,R)}const K=(t,n)=>t+n+(t>n?1:0)>>1;function V(t,n,e){const o=t>=n&&n>=e?1:t<=n&&n<=e?-1:0;if(!o)return 0;let r=(4*(t*=o)-3*(e*=o)-(n*=o)+6)/12|0;return r-(1&r)>2*(t-n)&&(r=2*(t-n)+1),r+(1&r)>2*(n-e)&&(r=2*(n-e)),o*r}function W(t,n){const{w:e,h:o,data:r}=t,s=n?e:o,c=s+1>>1,i=s-c,l=n?c:e,f=n?o:c,a=n?i:e,u=n?o:i,h=new Int16Array(l*f),y=new Int16Array(a*u),w=n?1:e;for(let t=0;t<f;t++)for(let o=0;o<l;o++){const c=n?o:t,f=n?t*e+2*o:2*t*e+o;if(c===i){h[t*l+o]=r[f];continue}const u=r[f],p=r[f+w],d=K(u,p),M=c+1<i?K(r[f+2*w],r[f+3*w]):1&s?r[f+2*w]:d;h[t*l+o]=d,y[t*a+o]=u-p-V(c?r[f-w]:d,d,M)}const p=(e,o,r)=>({w:o,h:r,rt:t.rt+ +n,st:t.st+!n,data:e});return[p(h,l,f),p(y,a,u)]}function $(t){const n=[],e=t.length;let o=t[0].w,r=t[0].h;for(e>2&&t[1].w===o&&t[1].h===r&&(n.push({$:!0,tt:!1,D:1,H:2}),n.push({$:!1,tt:!1,D:1,H:2})),!(o>r)&&r>8&&(n.push({$:!1,tt:!0,D:0,H:e}),r=r+1>>1);o>8||r>8;)o>8&&(n.push({$:!0,tt:!0,D:0,H:e}),o=o+1>>1),r>8&&(n.push({$:!1,tt:!0,D:0,H:e}),r=r+1>>1);return n}const tt=1024,nt=8192;function*et(t,n,o,{quality:r=90,shape:s=F(t,n,o,{palette:!1}),colorSpace:c}={}){const i=function(t){return t>=100?0:t>=30?.1+.09*(100-t):53/3e3*t*t-1.15*t+25}(r);if(!(i>0))throw new Error("lossy encoding needs a quality below 100");const{colour:l,alpha:f,channels:a}=s,u=n*o,d=Array.from({length:a},()=>new Int16Array(u));for(let n=0,e=0;e<u;e++,n+=4){if(3===l){const o=t[n],r=t[n+1],s=t[n+2],c=o-s,i=s+(c>>1),l=r-i;d[0][e]=i+(l>>1),d[1][e]=c,d[2][e]=l}else d[0][e]=t[n];f&&(d[a-1][e]=t[n+3])}const M=d.map((t,e)=>({w:n,h:o,rt:0,st:0,data:t,component:3===l?e:0===e?0:3})),A=1===n&&o>tt&&3===l,g=A?$(M).filter(t=>!t.$):1!==n&&1!==o||3!==l?[]:$(M),m=function(t,n=$(t)){const e=t.map(t=>({...t,level:0,ct:!1}));for(const t of n){const n=t.D+t.H-1,o=t.tt?n+1:e.length;for(let r=t.D;r<=n;r++){const[n,s]=W(e[r],t.$),c=e[r].level+1;e[r]={...n,level:c,ct:e[r].ct,component:e[r].component},e.splice(o+(r-t.D),0,{...s,level:c,ct:!0,component:e[r].component})}}return e}(M,g.length?g:void 0),b=t=>t.ct?0:J,U=t=>function(t,n,e,o){let r=Math.min(16,n+e);if(r>0&&r--,3===t)return 1;const s=.25*function(t){let n=1;for(let e=0;e<24;e++)n=(4*n+t/(n*n*n*n))/5;return t*n}(o),c=1<<r,i=1===t||2===t?.35*s*Math.max(.5,1024/c):.35*s*1.1*(163.84/c);return Math.max(1,Math.floor(i))}(t.component,t.rt+(A&&t.component>0&&t.component<3?1:0),t.st,i),I=[...3===l?[{type:"rct",D:0,F:6}]:[],{type:"squeeze",W:g}],S=w(n,o,tt),x=S.A*S.m,C=S.p*S.M;let T=0;for(;T<m.length&&m[T].w<=tt&&m[T].h<=tt;)T++;const v=(t,n,e,o)=>{const r=n>>t.rt,s=e>>t.st;return{x:r,y:s,w:Math.min(t.w,r+(o>>t.rt))-r,h:Math.min(t.h,s+(o>>t.st))-s}},E=[{index:0,Y:0,it:[]}];for(let t=0;t<T;t++)E[0].it.push({c:t,rect:{x:0,y:0,w:m[t].w,h:m[t].h}});if(!S.U){for(let t=0;t<x;t++){const n=t%S.A,e=t/S.A|0,o={index:1+t,Y:1+x+t,it:[]};for(let t=T;t<m.length;t++){const r=m[t];if(Math.min(r.rt,r.st)<3)continue;const s=v(r,n*nt,e*nt,nt);s.w>0&&s.h>0&&o.it.push({c:t,rect:s})}E.push(o)}for(let t=0;t<C;t++){const n=t%S.p,e=t/S.p|0,o={index:2+x+t,Y:1+3*x+17+t,it:[]};for(let t=T;t<m.length;t++){const r=m[t];if(Math.min(r.rt,r.st)>2)continue;const s=v(r,n*tt,e*tt,tt);s.w>0&&s.h>0&&o.it.push({c:t,rect:s})}E.push(o)}}for(const t of E)for(const n of t.it){const t=m[n.c];n.lt=G(b(t),0,U(t)),n.lt.channel=n.c}const q=E.filter(t=>t.it.length),L=1===q.length?j(q[0].it.map(t=>t.lt)):function(t){return R(t,1)}(q.map(t=>({Y:t.Y,k:j(t.it.map(t=>t.lt))}))),X=new Int16Array(1048576),N=t=>{const n=m[t.c],e=t.rect;if(e.w===n.w&&e.h===n.h)return n.data;for(let t=0;t<e.h;t++)X.set(n.data.subarray((e.y+t)*n.w+e.x,(e.y+t)*n.w+e.x+e.w),t*e.w);return X},_=[...new Set(E.flatMap(t=>t.it.map(t=>t.c)))].sort((t,n)=>t-n),P=new Map(_.map((t,n)=>[t,n])),O=_.map(()=>new Uint32Array(z)),B=1+2*E.length;let Q=1;yield Q/B;for(const t of E){for(const n of t.it)D(null,O[P.get(n.c)],N(n),n.rect.w,n.rect.h,n.lt);yield++Q/B}const H=new e(256);h(H,n,o,l,f,{colorSpace:c}),y(H,{alpha:f,shift:n>256||o>256?3:1});const K=Array.from({length:S.U?1:2+x+C},()=>null),V=K[0]=new e(65536);V.write(1,1),V.write(1,1);const et=Y(V,Z(V,L),O,t=>P.get(t.channel));k(V,{nt:!0,et:I});for(const t of E){let n=K[t.index];n||(n=K[t.index]=new e(4096),t.it.length&&k(n,{nt:!0,et:[]}));for(const e of t.it)D(n,et[P.get(e.c)+1].code,N(e),e.rect.w,e.rect.h,e.lt);yield++Q/B}return p(H,function(t){return t.map(t=>t?t.finish():new Uint8Array(0))}(K))}const ot=[-500,-392,-255,-191,-127,-95,-63,-47,-31,-23,-15,-11,-7,-4,-3,-1,0,1,3,5,7,11,15,23,31,47,63,95,127,191,255,392,500],rt=Int8Array.from({length:1003},(t,n)=>ot.filter(t=>t<n-501).length),st=Int32Array.from({length:64},(t,n)=>Math.floor(16777216/(n+1))),ct=(t,n)=>{const e=Math.max(0,26-Math.clz32(t+1));return 4+(n*st[t>>e]>>e)},it=[12,13].map(t=>Int32Array.from({length:2048},(n,e)=>ct(e,t))),lt=(t,n)=>t<2048?it[n-12][t]:ct(t,n),ft=new Int32Array(ot.length+1);let at;function ut(t,n,e,o,s,c=0,i=ft,l,f){const a=o+2,u=at&&at[0].length>=2*a?at:Array.from({length:6},(t,n)=>new Int32Array(n<5?2*a:8));at=null;for(const t of u)t.fill(0);const[h,y,w,p,d,M]=u;let A=0;const g=(e,o,r=P,s=0)=>{t?q(t,n[e],r,o,s):v(r,o,n[e],s)},m=()=>{if(A){if(A>_.minLength)g(M[0],0),g(M[1],A-_.minLength-1,_.O,_.G);else for(let t=0;t<A;t++)g(M[t],0);A=0}};for(let t=0,n=0;t<s;t++){const s=1&t?0:a,u=1&t?a:0;for(let a=0;a<o;a++,n++){const b=a?e[n-1]:t?e[n-o]:0,U=t?e[n-o]:b,I=a+1<o&&t?e[n-o+1]:U,S=u+a,x=a<o-1?S+1:S,C=a?S-1:S;let T=lt(h[S]+h[x]+h[C],13),v=lt(y[S]+y[x]+y[C],12),E=lt(w[S]+w[x]+w[C],12),q=lt(p[S]+p[x]+p[C],12);const L=8*U,X=8*b,J=8*I,N=a?d[s+a-1]:0,_=d[S],P=d[C],z=d[x],G=_+N;let O=N;Math.abs(_)>Math.abs(O)&&(O=_),Math.abs(P)>Math.abs(O)&&(O=P),Math.abs(z)>Math.abs(O)&&(O=z);const R=i[rt[O<-501?0:O>501?1002:O+501]];f&&(f[n]=O);const j=X+J-L,Z=L-(16*(G+z)>>5),B=X-(10*(G+P)>>5),k=L-(7*(P+_+z)>>5),Y=27-Math.clz32(T+v+E+q);T>>=Y,v>>=Y,E>>=Y,q>>=Y;const D=T+v+E+q;let F=Math.floor(((D>>1)-1+j*T+Z*v+B*E+k*q)*st[D-1]/16777216);if((_^N|_^P)<=0){const t=Math.min(X,J,L),n=Math.max(X,J,L);F=F<t?t:F>n?n:F}const Q=e[n],H=Q-(F+3>>3)-c;l?l[n]=r(H):0===H?(A<8&&(M[A]=R),A++):(m(),g(R,r(H)));const K=8*Q,V=Math.abs(j-K)+3>>3,W=Math.abs(Z-K)+3>>3,$=Math.abs(B-K)+3>>3,tt=Math.abs(k-K)+3>>3;d[s+a]=F-K,h[s+a]=V,y[s+a]=W,w[s+a]=$,p[s+a]=tt,h[u+a+1]+=V,y[u+a+1]+=W,w[u+a+1]+=$,p[u+a+1]+=tt}}m(),at=u}const ht=t=>Math.max(0,t<224?t-1:t-236),yt=t=>{const n=I(t),o=new e(128);S(o,n);let r=o.u;for(let e=0;e<t.length;e++)t[e]&&(r+=t[e]*(n.C[e]+ht(e)));return r};function wt(t,n,e,o,s){const c=new Uint32Array(n*e);if(6===o)return ut(null,null,t,n,e,0,void 0,c,s),c;for(let s=0,i=0;s<e;s++)for(let e=0;e<n;e++,i++){const l=e?t[i-1]:s?t[i-n]:0,f=s?t[i-n]:l,a=e&&s?t[i-n-1]:l,u=3===o?(l+f)/2|0:Math.max(Math.min(l,f),Math.min(Math.max(l,f),l+f-a));c[i]=r(t[i]-u)}return c}function pt(t,n,e=1){const o=Array.from({length:e},()=>new Uint32Array(z)),r=[],s=(t,e,s=0)=>{const c=n?n[t]:0;r.push(e,s,c),v(s?_.O:P,e,o[c],s)};for(let n=0;n<t.length;){let e=1;if(!t[n])for(;n+e<t.length&&!t[n+e];)e++;if(s(n,t[n]),e>_.minLength)s(n+1,e-1-_.minLength,_.G);else for(let t=1;t<e;t++)s(n+t,0);n+=e}return{ot:o,it:r}}function dt(t,n,e,o,r){const s=pt(t,Uint8Array.from(n,t=>{let n=0,e=r.length;for(;n<e;){const o=n+e>>1;t>r[o]?n=o+1:e=o}return n}),r.length+1),c=s.ot.length,i=s.ot.map(t=>t.some(t=>t)),l=new Float64Array(c+1).fill(1/0),f=new Uint8Array(c+1);l[0]=0;for(let t=1;t<=c;t++){const n=new Uint32Array(z);let e=0;for(let o=t-1;o>=0;o--){if(i[o]){for(let t=0;t<z;t++)n[t]+=s.ot[o][t];e=yt(n)}const r=l[o]+e+24;r<l[t]&&(l[t]=r,f[t]=o)}}const a=[];for(let t=c;t;t=f[t])a.unshift([f[t],t]);const u=a.map(()=>G(e)),h=new Uint8Array(c),y=a.map(([t,n],e)=>{const o=new Uint32Array(z);for(let r=t;r<n;r++){h[r]=e;for(let t=0;t<z;t++)o[t]+=s.ot[r][t]}return o});for(let t=2;t<s.it.length;t+=3)s.it[t]=h[s.it[t]];const w=(t,n)=>{if(t===n)return u[t];const e=t+n>>1;return O(o,r[a[e][1]-1],w(e+1,n),w(t,e))};return{leaves:u,k:w(0,a.length-1),ot:y,it:s.it}}const Mt=[-127,-31,-7,-1,0,1,7,31,127],At=[-191,-127,-63,-31,-7,-1,0,7,31,63,127,191,255];function gt(t,n,e,o){const r=new Int32Array(n*e);for(let s=0,c=0;s<e;s++)for(let e=0;e<n;e++,c++){const i=e?t[c-1]:s?t[c-n]:0,l=s?t[c-n]:i,f=e&&s?t[c-n-1]:i;r[c]=9===o?i+l-f:i-f}return r}function mt(t){const n=new e(512),o=Y(n,Z(n,t.k),t.ot,n=>t.leaves.indexOf(n));let r=n.u;return t.ot.forEach((t,n)=>{const{C:e}=o[n+1].code;for(let n=0;n<t.length;n++)r+=t[n]*((e[n]||0)+ht(n))}),r}function bt(t,n,e,o){return t.map(t=>{let r;for(const s of[5,3,6]){const c=6===s?new Int32Array(n*e):null,i=wt(t,n,e,s,c),l=G(s),f=[{...pt(i),leaves:[l],k:l}];if(c&&f.push(dt(i,c,s,15,Mt)),o>=6&&5===s)for(const o of[9,10]){const r=gt(t,n,e,o),c=9===o?At:Mt;f.push(dt(i,r,s,o,c))}for(const t of f){const n=mt(t);(!r||n<r.ft)&&(r={...t,ft:n})}}return r})}function Ut(t,n){const e=n.flatMap(t=>t.leaves),o=Y(t,Z(t,j(n.map(t=>t.k))),n.flatMap(t=>t.ot),t=>e.indexOf(t));return()=>{for(let r=0;r<n.length;r++){const{it:s}=n[r],c=n[r].leaves.map(t=>o[e.indexOf(t)+1].code);for(let n=0;n<s.length;n+=3){const e=s[n+1];q(t,c[s[n+2]],e?_.O:P,s[n],e)}}}}function*It(t,n,o,r,s,c=4,i=!1){const{channels:f}=r,u=w(n,o),d=u.p*u.M,M=i?r.palette:null;if(i&&!M)return null;const A=Array.from({length:M?1:f},()=>new Int16Array(65536)),g=new e(256);h(g,n,o,r.colour,r.alpha,{colorSpace:s}),y(g,{alpha:r.alpha});const m=M?[{type:"palette",D:0,H:f,K:M.colours.length}]:f>=3?[{type:"rct",D:0,F:6}]:[];let b;if(M){const t=M.colours.length,n=new Int16Array(t*f);M.colours.forEach((e,o)=>{for(let r=0;r<f;r++)n[r*t+o]=M.byte(e,r)}),b=bt([n],t,f,c)}const U=new e(4096),I=[];let S=0;const x=t=>{S+=Math.ceil(t.u/8),l(S),I.push(t.finish())};if(U.write(1,1),U.write(1,1),!u.U){let t;if(b)t=Ut(U,b);else{const t=A.map(()=>G(5));Y(U,Z(U,j(t)),A.map(()=>new Uint32Array(z)),n=>t.indexOf(n))}k(U,{et:m}),t&&t(),x(U);for(let t=0;t<u.A*u.m+1;t++)I.push(new Uint8Array(0))}for(let r=0;r<d;r++){const s=r%u.p*a,i=(r/u.p|0)*a,l=Math.min(a,n-s),h=Math.min(a,o-i);for(let e=0;e<h;e++)for(let o=0,r=e*l,c=4*((i+e)*n+s);o<l;o++,r++,c+=4)if(M)A[0][r]=M.index.get(M.key(c));else if(f>=3){const n=t[c]-t[c+2],e=t[c+2]+(n>>1),o=t[c+1]-e;A[0][r]=e+(o>>1),A[1][r]=n,A[2][r]=o,4===f&&(A[3][r]=t[c+3])}else A[0][r]=t[c],2===f&&(A[1][r]=t[c+3]);const y=bt(A,l,h,c),w=M&&u.U?[...b,...y]:y,p=u.U?U:new e(l*h*A.length+256);u.U||k(p,{nt:!1});const g=Ut(p,w);if(u.U&&k(p,{et:m}),g(),x(p),yield(r+1)/d)return null}return p(g,I)}function St(t,n,e,o){return function(t){let n;for(;!(n=t.next()).done;);return n.value}(xt(t,n,e,o))}function xt(n,e,r,s){const{quality:c,colorSpace:a}=function(t={},n=100){if(null===t||"object"!=typeof t)throw i("JXL_INPUT","Options are an object: {quality, colorSpace}.");if(void 0!==t.quality&&(n=t.quality),"number"!=typeof n||!Number.isFinite(n)||n<1||n>100)throw i("JXL_INPUT","Quality is a number from 1 to 100.");const e=void 0===t.colorSpace?"srgb":t.colorSpace;if("srgb"!==e&&"display-p3"!==e)throw i("JXL_INPUT","The colour space is srgb or display-p3.");return{quality:n,colorSpace:e}}(s),u=void 0===s?.effort?1:s.effort;if(!Number.isInteger(u)||u<1||u>9)throw i("JXL_INPUT","Effort is a whole number from 1 to 9.");return function(n,e,o,r){if(function(n,e,o=t){if(!Number.isInteger(n)||!Number.isInteger(e)||n<1||e<1)throw i("JXL_INPUT","A picture is at least one pixel wide and high, in whole pixels.");if(n>o.edge||e>o.edge)throw i("JXL_DIMENSIONS","A picture is at most "+o.edge+" pixels on a side.");if(n*e>o.pixels)throw i("JXL_DIMENSIONS","A picture is at most "+o.pixels.toLocaleString("en-US")+" pixels.")}(e,o,r),!(n instanceof Uint8Array||n instanceof Uint8ClampedArray))throw i("JXL_INPUT","Pixels are a Uint8Array or Uint8ClampedArray of RGBA bytes.");if(n.length!==e*o*4)throw i("JXL_INPUT","Pixels are width * height * 4 bytes: straight (not premultiplied) RGBA, row by row.")}(n,e,r),function(t){const n=function*(){let e,o;for(;!(e=f(()=>t.next(n.hurry))).done;)yield o=e.value;return o<1&&(yield 1),n.bytes=function(t){return l(t.length),t}(e.value)}();return n.bytes=null,n.hurry=!1,n}(function*(t,n,e,r,s,c){const i=F(t,n,e);if(r<100){if(!i.palette)return yield*et(t,n,e,{quality:r,shape:i,colorSpace:s});const c=yield*o(Q(t,n,e,{shape:i,colorSpace:s}),0,2);let l;try{l=yield*o(et(t,n,e,{quality:r,shape:i,colorSpace:s}),1,2)}catch(t){if("JXL_SIZE"!==t.code&&(!(t instanceof RangeError)||t.code))throw t;l=c}return c.length<=l.length?c:l}if(c<2)return yield*Q(t,n,e,{shape:i,colorSpace:s});let l=yield*o(Q(t,n,e,{shape:i,colorSpace:s}),0,2);if(c>=4){const o=[Et(t,n,e,i.palette?{...i,palette:null}:i,s,3)];i.palette&&o.push(It(t,n,e,i,s,4,!0)),c>=6&&(o.push(It(t,n,e,i,s,6)),i.palette&&o.push(It(t,n,e,i,s,6,!0)));for(let t=0;t<o.length;t++)try{let n,e=!1;for(;!(n=o[t].next(e)).done;)e=yield.5+(t+n.value)/(2*o.length);if(n.value&&n.value.length<l.length&&(l=n.value),e)return l}catch(t){if("JXL_SIZE"!==t.code&&(!(t instanceof RangeError)||t.code))throw t}return l}try{const r=yield*o(Et(t,n,e,i.palette?{...i,palette:null}:i,s,c),1,2);r&&r.length<l.length&&(l=r)}catch(t){if("JXL_SIZE"!==t.code&&(!(t instanceof RangeError)||t.code))throw t}return l}(n,e,r,c,a,u))}const Ct=t=>Math.max(0,t<224?t-1:t-236);function Tt(t){const n=I(t),o=new e(128);S(o,n);let r=o.u;for(let e=0;e<t.length;e++)t[e]&&(r+=t[e]*(n.C[e]+Ct(e)));return r}const vt=t=>{const n=new Uint32Array(z);for(const e of t)for(let t=0;t<z;t++)n[t]+=e[t];return n};function*Et(t,n,o,r,s,c){const{channels:i}=r,l=w(n,o),f=l.p*l.M,u=Array.from({length:i},()=>new Int16Array(65536)),d=(e,o,r,s)=>{for(let c=0;c<s;c++){let s=4*((o+c)*n+e),l=c*r;for(let n=0;n<r;n++,s+=4,l++)if(i>=3){const n=t[s],e=t[s+1],o=t[s+2],r=n-o,c=o+(r>>1),f=e-c;u[0][l]=c+(f>>1),u[1][l]=r,u[2][l]=f,4===i&&(u[3][l]=t[s+3])}else u[0][l]=t[s],2===i&&(u[1][l]=t[s+3])}},M=(t,e)=>{const r=t%l.p*a,s=(t/l.p|0)*a,c=Math.min(a,n-r),i=Math.min(a,o-s);d(r,s,c,i),e(c,i)},A=Math.min(n,32),g=Math.min(o,32),m=[J,X].map(t=>({R:t,ot:u.map(()=>new Uint32Array(z))}));for(const t of[0,.5,1]){d(Math.floor((n-A)*t),Math.floor((o-g)*t),A,g);for(const t of m)for(let n=0;n<i;n++)D(null,t.ot[n],u[n],A,g,G(t.R))}const b=u.map((t,n)=>G(m[Tt(m[1].ot[n])<Tt(m[0].ot[n])?1:0].R)),U=ot.length+1,I=Int32Array.from({length:U},(t,n)=>n),S=u.map(()=>new Uint32Array(z)),x=u.map(()=>Array.from({length:U},()=>new Uint32Array(z)));let C=0;for(let t=0;t<f;t++)if(M(t,(t,n)=>{for(let e=0;e<i;e++)D(null,S[e],u[e],t,n,b[e]),ut(null,x[e],u[e],t,n,0,I)}),yield++C/(2*f))return null;const T=u.map((t,n)=>{const e={ut:Tt(S[n]),leaves:[b[n]],ot:[S[n]],cuts:[]},o=vt(x[n]),r=Tt(o);return r<e.ut?{ut:r,leaves:[G(6)],ot:[o],cuts:[]}:e}),v=c<3?T:T.map((t,n)=>{const e=x[n].map(t=>t.some(t=>t)),o=new Float64Array(U+1).fill(1/0),r=new Int32Array(U+1);o[0]=0;for(let t=1;t<=U;t++){const s=new Uint32Array(z);let c=0;for(let i=t-1;i>=0;i--){if(e[i]){const t=x[n][i];for(let n=0;n<z;n++)s[n]+=t[n];c=Tt(s)}const l=o[i]+c+24;l<o[t]&&(o[t]=l,r[t]=i)}}if(!(o[U]<t.ut))return t;const s=[];for(let t=U;t>0;t=r[t])s.unshift([r[t],t]);return{ut:o[U],leaves:s.map(()=>G(6)),ot:s.map(([t,e])=>vt(x[n].slice(t,e))),cuts:s.slice(0,-1).map(([,t])=>ot[t-1]),ht:Int32Array.from({length:U},(t,n)=>s.findIndex(([t,e])=>n>=t&&n<e))}}),E=[T,v].filter((t,n)=>n?t.some((t,n)=>t!==T[n]):t.some((t,n)=>t.leaves[0]!==b[n]));if(!E.length)return null;const q=t=>({k:j(t.map(t=>((t,n)=>{const e=(o,r)=>{if(o===r)return t[o];const s=o+r>>1;return O(15,n[s],e(s+1,r),e(o,s))};return e(0,t.length-1)})(t.leaves,t.cuts))),leaves:t.flatMap(t=>t.leaves),ot:t.flatMap(t=>t.ot)}),L=t=>{const{k:n,leaves:o,ot:r}=q(t),s=new e(4096),c=Y(s,Z(s,n),r,t=>o.indexOf(t));let i=s.u;return r.forEach((t,n)=>{const{C:e}=c[n+1].code;for(let n=0;n<t.length;n++)t[n]&&(i+=t[n]*(e[n]+Ct(n)))}),i};let N=E;if(E.length>1){const[t,n]=E.map(L),e=27*(l.U?1:f+1)+8;n+e<=t?N=[E[1]]:t+e<=n?N=[E[0]]:n<t&&(N=[E[1],E[0]])}let _=0,P=!1;const R=()=>.5+ ++_/(2*N.length*f);let B=null;for(const t of N){const n=yield*F(t);if(!n)return B;if((!B||n.length<B.length||n.length===B.length&&t===E[0])&&(B=n),P)return B}return B;function*F(t){const{k:c,leaves:a,ot:w}=q(t),d=i>=3?[{type:"rct",D:0,F:6}]:[],A=new e(256);h(A,n,o,r.colour,r.alpha,{colorSpace:s}),y(A,{alpha:r.alpha});const g=new e(4096);g.write(1,1),g.write(1,1);const m=Y(g,Z(g,c),w,t=>a.indexOf(t));k(g,{nt:!0,et:d});const b=t.map(t=>t.leaves.map(t=>m[a.indexOf(t)+1].code)),U=(n,e,o,r)=>{const s=t[e];6===s.leaves[0].R?ut(n,b[e],u[e],o,r,0,s.ht):D(n,b[e][0],u[e],o,r,s.leaves[0])},I=[];if(l.U)M(0,(t,n)=>{for(let e=0;e<i;e++)U(g,e,t,n)}),P=yield R(),I.push(g.finish());else{I.push(g.finish());for(let t=0;t<l.A*l.m+1;t++)I.push(new Uint8Array(0));for(let t=0;t<f;t++)if(M(t,(t,n)=>{const o=new e(t*n*i+64);k(o,{nt:!0,et:[]});for(let e=0;e<i;e++)U(o,e,t,n);I.push(o.finish())}),(P=yield R())&&t+1<f)return null}return p(A,I)}}export{t as LIMITS,St as encode,xt as encodeSteps};
package/effort.mjs ADDED
@@ -0,0 +1,240 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Rapier JXL's effort door: the core's `encode` with a search above it. {effort: 1} (the default) writes the core's
3
+ // stream byte for byte; a higher effort prices more candidates for every channel of an exact picture and keeps the
4
+ // smallest stream: never larger than the effort below it, effort 1's on a tie. The rungs, each from its effort up:
5
+ // 2. the specification's self-correcting (weighted) predictor, one context per channel;
6
+ // 3. the weighted predictor with its channel's tokens split by the predictor's own error, neighbouring intervals of
7
+ // libjxl's cut points merged wherever a shared prefix code costs less.
8
+ // 4. palette indices with local trees and histograms, the predictor and error intervals learned per group;
9
+ // 6. direct and palette planes with splits on the unclamped gradient and west-minus-northwest difference.
10
+ // A lossy request is effort 1's. Above effort 1 an exact picture's job spends its first half (fractions up to 0.5)
11
+ // writing effort 1's stream and its second searching, so a caller's clock can tell the search's own pace; `hurry`
12
+ // ends the search at its next step with the smallest stream written so far. Every choice is integer arithmetic: the
13
+ // same input and options write the same bytes everywhere.
14
+ import {LIMITS, BitWriter, complete, part} from './bits.mjs';
15
+ import {writeImageHeader, writeModularFrameHeader, groupLayout, assembleCodestream, GROUP_DIM} from './frame.mjs';
16
+ import {buildCode, writePrefixCode} from './prefix.mjs';
17
+ import {AVERAGE_PREDICTOR, GRADIENT_PREDICTOR, ALPHABET, leaf, split, channelTree, writeTree, writeModularHeader, writeChannelHistograms, codeChannel} from './modular.mjs';
18
+ import {inspectPixels, losslessSteps} from './lossless.mjs';
19
+ import {lossySteps} from './lossy.mjs';
20
+ import {WEIGHTED_PREDICTOR, WEIGHTED_PROPERTY, WEIGHTED_CUTS, codeWeighted} from './weighted.mjs';
21
+ import {fault, admitOptions, admitPixels, job} from './admit.mjs';
22
+ import {localSteps} from './local.mjs';
23
+
24
+ export {LIMITS};
25
+
26
+ export function encode(data, width, height, options) { return complete(encodeSteps(data, width, height, options)); }
27
+
28
+ // The options as every door reads them, and the effort, which this door alone reads (the core, one rung, does not): a
29
+ // whole number from 1 to 9, 1 when absent.
30
+ export function encodeSteps(data, width, height, options) {
31
+ const {quality, colorSpace} = admitOptions(options), effort = options?.effort === undefined ? 1 : options.effort;
32
+ if (!Number.isInteger(effort) || effort < 1 || effort > 9) throw fault('JXL_INPUT', 'Effort is a whole number from 1 to 9.');
33
+ admitPixels(data, width, height);
34
+ return job(effortSteps(data, width, height, quality, colorSpace, effort));
35
+ }
36
+
37
+ // Effort 1 is the core's work (index.mjs), step for step; the rungs run after it and keep a smaller stream if they
38
+ // find one. A search that runs out of memory leaves the smallest completed stream standing.
39
+ function* effortSteps(data, width, height, quality, colorSpace, effort) {
40
+ const shape = inspectPixels(data, width, height);
41
+ if (quality < 100) {
42
+ if (!shape.palette) return yield* lossySteps(data, width, height, {quality, shape, colorSpace});
43
+ const exact = yield* part(losslessSteps(data, width, height, {shape, colorSpace}), 0, 2);
44
+ let bytes;
45
+ try { bytes = yield* part(lossySteps(data, width, height, {quality, shape, colorSpace}), 1, 2); }
46
+ catch (error) { if (error.code !== 'JXL_SIZE' && (!(error instanceof RangeError) || error.code)) throw error; bytes = exact; }
47
+ return exact.length <= bytes.length ? exact : bytes;
48
+ }
49
+ if (effort < 2) return yield* losslessSteps(data, width, height, {shape, colorSpace});
50
+ let best = yield* part(losslessSteps(data, width, height, {shape, colorSpace}), 0, 2);
51
+ if (effort >= 4) {
52
+ const searches = [searchSteps(data, width, height, shape.palette ? {...shape, palette: null} : shape, colorSpace, 3)];
53
+ if (shape.palette) searches.push(localSteps(data, width, height, shape, colorSpace, 4, true));
54
+ if (effort >= 6) {
55
+ searches.push(localSteps(data, width, height, shape, colorSpace, 6));
56
+ if (shape.palette) searches.push(localSteps(data, width, height, shape, colorSpace, 6, true));
57
+ }
58
+ for (let i = 0; i < searches.length; i++) {
59
+ try {
60
+ let step, hurried = false;
61
+ while (!(step = searches[i].next(hurried)).done) hurried = yield 0.5 + (i + step.value) / (2 * searches.length);
62
+ if (step.value && step.value.length < best.length) best = step.value;
63
+ if (hurried) return best;
64
+ } catch (error) { if (error.code !== 'JXL_SIZE' && (!(error instanceof RangeError) || error.code)) throw error; }
65
+ }
66
+ return best;
67
+ }
68
+ try {
69
+ const bytes = yield* part(searchSteps(data, width, height, shape.palette ? {...shape, palette: null} : shape, colorSpace, effort), 1, 2);
70
+ if (bytes && bytes.length < best.length) best = bytes;
71
+ } catch (error) { if (error.code !== 'JXL_SIZE' && (!(error instanceof RangeError) || error.code)) throw error; }
72
+ return best;
73
+ }
74
+
75
+ // A token's raw bits: a residual token s above zero carries s - 1 of them, an LZ77 length token above 235 s - 236.
76
+ const raw = s => Math.max(0, s < 224 ? s - 1 : s - 236);
77
+ // A histogram's complete cost in bits: its prefix code's header and every token's code and raw bits.
78
+ function cost(freqs) {
79
+ const code = buildCode(freqs), writer = new BitWriter(128);
80
+ writePrefixCode(writer, code);
81
+ let bits = writer.bitLength;
82
+ for (let s = 0; s < freqs.length; s++) if (freqs[s]) bits += freqs[s] * (code.lengths[s] + raw(s));
83
+ return bits;
84
+ }
85
+ const sum = histograms => { const out = new Uint32Array(ALPHABET); for (const h of histograms) for (let s = 0; s < ALPHABET; s++) out[s] += h[s]; return out; };
86
+
87
+ // The direct plan of lossless.mjs (YCoCg, 256-pixel groups), every channel counted over the whole picture under effort
88
+ // 1's predictor (chosen by samples, as the core chooses it) and under the weighted one; each rung's plan takes every
89
+ // channel's cheapest, and the plans are written the cheapest way. Nothing written (null) when no plan leaves effort 1.
90
+ function* searchSteps(rgba, width, height, shape, colorSpace, effort) {
91
+ const {channels} = shape, layout = groupLayout(width, height), groups = layout.groupsX * layout.groupsY;
92
+ const planes = Array.from({length: channels}, () => new Int16Array(GROUP_DIM * GROUP_DIM));
93
+ const fill = (x0, y0, w, h) => {
94
+ for (let y = 0; y < h; y++) {
95
+ let i = ((y0 + y) * width + x0) * 4, at = y * w;
96
+ for (let x = 0; x < w; x++, i += 4, at++) {
97
+ if (channels >= 3) {
98
+ const r = rgba[i], g = rgba[i + 1], b = rgba[i + 2];
99
+ const co = r - b, tmp = b + (co >> 1), cg = g - tmp;
100
+ planes[0][at] = tmp + (cg >> 1); planes[1][at] = co; planes[2][at] = cg;
101
+ if (channels === 4) planes[3][at] = rgba[i + 3];
102
+ } else { planes[0][at] = rgba[i]; if (channels === 2) planes[1][at] = rgba[i + 3]; }
103
+ }
104
+ }
105
+ };
106
+ const group = (g, visit) => {
107
+ const x0 = g % layout.groupsX * GROUP_DIM, y0 = (g / layout.groupsX | 0) * GROUP_DIM, w = Math.min(GROUP_DIM, width - x0), h = Math.min(GROUP_DIM, height - y0);
108
+ fill(x0, y0, w, h);
109
+ visit(w, h);
110
+ };
111
+ // Effort 1's predictor per channel: three 32-pixel samples priced under gradient and average, as the core does.
112
+ const sw = Math.min(width, 32), sh = Math.min(height, 32);
113
+ const sampled = [GRADIENT_PREDICTOR, AVERAGE_PREDICTOR].map(predictor => ({predictor, freqs: planes.map(() => new Uint32Array(ALPHABET))}));
114
+ for (const fraction of [0, 0.5, 1]) {
115
+ fill(Math.floor((width - sw) * fraction), Math.floor((height - sh) * fraction), sw, sh);
116
+ for (const candidate of sampled) for (let c = 0; c < channels; c++) codeChannel(null, candidate.freqs[c], planes[c], sw, sh, leaf(candidate.predictor));
117
+ }
118
+ const first = planes.map((_, c) => leaf(sampled[cost(sampled[1].freqs[c]) < cost(sampled[0].freqs[c]) ? 1 : 0].predictor));
119
+ // Every channel counted under effort 1's predictor and under the weighted one, its tokens kept by error interval.
120
+ // A step is a group of the counting pass (the first half of the search's fractions) or of a plan's writing (the
121
+ // second half, shared by the plans written), so the pace of the steps holds whether one plan is written or two.
122
+ const contexts = WEIGHTED_CUTS.length + 1, identity = Int32Array.from({length: contexts}, (_, k) => k);
123
+ const firstCounts = planes.map(() => new Uint32Array(ALPHABET)), intervals = planes.map(() => Array.from({length: contexts}, () => new Uint32Array(ALPHABET)));
124
+ let done = 0;
125
+ for (let g = 0; g < groups; g++) {
126
+ group(g, (w, h) => {
127
+ for (let c = 0; c < channels; c++) {
128
+ codeChannel(null, firstCounts[c], planes[c], w, h, first[c]);
129
+ codeWeighted(null, intervals[c], planes[c], w, h, 0, identity);
130
+ }
131
+ });
132
+ if (yield ++done / (2 * groups)) return null;
133
+ }
134
+ // Rung 2: each channel's cheaper of effort 1's predictor and the weighted one in one context.
135
+ const rung2 = planes.map((_, c) => {
136
+ const plan = {cost: cost(firstCounts[c]), leaves: [first[c]], freqs: [firstCounts[c]], cuts: []};
137
+ const whole = sum(intervals[c]), single = cost(whole);
138
+ return single < plan.cost ? {cost: single, leaves: [leaf(WEIGHTED_PREDICTOR)], freqs: [whole], cuts: []} : plan;
139
+ });
140
+ // Rung 3: or its intervals grouped into runs of neighbours, the grouping of least cost found exactly by dynamic
141
+ // programming. best[j] is the least cost of intervals 0..j-1 grouped, `from[j]` where its last group begins; an
142
+ // interval no token reaches leaves the merged cost as it was.
143
+ const rung3 = effort < 3 ? rung2 : rung2.map((plan, c) => {
144
+ const filled = intervals[c].map(h => h.some(v => v));
145
+ const best = new Float64Array(contexts + 1).fill(Infinity), from = new Int32Array(contexts + 1);
146
+ best[0] = 0;
147
+ for (let j = 1; j <= contexts; j++) {
148
+ const merged = new Uint32Array(ALPHABET);
149
+ let bits = 0;
150
+ for (let i = j - 1; i >= 0; i--) {
151
+ if (filled[i]) { const h = intervals[c][i]; for (let s = 0; s < ALPHABET; s++) merged[s] += h[s]; bits = cost(merged); }
152
+ // Every group is a leaf of the tree and an entry of the context map: 24 bits more, so an interval no token
153
+ // reaches joins its neighbour.
154
+ const price = best[i] + bits + 24;
155
+ if (price < best[j]) { best[j] = price; from[j] = i; }
156
+ }
157
+ }
158
+ if (!(best[contexts] < plan.cost)) return plan;
159
+ const groupsOf = [];
160
+ for (let j = contexts; j > 0; j = from[j]) groupsOf.unshift([from[j], j]);
161
+ return {cost: best[contexts], leaves: groupsOf.map(() => leaf(WEIGHTED_PREDICTOR)), freqs: groupsOf.map(([i, j]) => sum(intervals[c].slice(i, j))), cuts: groupsOf.slice(0, -1).map(([, j]) => WEIGHTED_CUTS[j - 1]),
162
+ contextOf: Int32Array.from({length: contexts}, (_, k) => groupsOf.findIndex(([i, j]) => k >= i && k < j))};
163
+ });
164
+ const candidates = [rung2, rung3].filter((plans, k) => k ? plans.some((plan, c) => plan !== rung2[c]) : plans.some((plan, c) => plan.leaves[0] !== first[c]));
165
+ if (!candidates.length) return null;
166
+ // The tree: one subtree per channel on the channel property, and inside a split channel a balanced tree on the
167
+ // weighted predictor's property, values above a cut to the left.
168
+ const byError = (leaves, cuts) => {
169
+ const build = (lo, hi) => { if (lo === hi) return leaves[lo]; const mid = (lo + hi) >> 1; return split(WEIGHTED_PROPERTY, cuts[mid], build(mid + 1, hi), build(lo, mid)); };
170
+ return build(0, leaves.length - 1);
171
+ };
172
+ const assemble = plans => ({tree: channelTree(plans.map(plan => byError(plan.leaves, plan.cuts))), leaves: plans.flatMap(plan => plan.leaves), freqs: plans.flatMap(plan => plan.freqs)});
173
+ // A plan's price in bits: its tree and histograms as written, and every token's code and raw bits. Two plans' streams
174
+ // differ by their prices and at most 27 bits a section more (a section's padding, and its size's field in the table
175
+ // of contents, 12 to 32 bits) and a byte: a plan that far cheaper is written alone, closer plans both.
176
+ const price = plans => {
177
+ const {tree, leaves, freqs} = assemble(plans), w = new BitWriter(4096);
178
+ const histograms = writeChannelHistograms(w, writeTree(w, tree), freqs, l => leaves.indexOf(l));
179
+ let bits = w.bitLength;
180
+ freqs.forEach((f, i) => { const {lengths} = histograms[i + 1].code; for (let s = 0; s < f.length; s++) if (f[s]) bits += f[s] * (lengths[s] + raw(s)); });
181
+ return bits;
182
+ };
183
+ let chosen = candidates;
184
+ if (candidates.length > 1) {
185
+ const [a, b] = candidates.map(price), margin = 27 * (layout.single ? 1 : groups + 1) + 8;
186
+ if (b + margin <= a) chosen = [candidates[1]];
187
+ else if (a + margin <= b) chosen = [candidates[0]];
188
+ else if (b < a) chosen = [candidates[1], candidates[0]];
189
+ }
190
+ let written = 0, hurried = false;
191
+ const next = () => 0.5 + ++written / (2 * chosen.length * groups);
192
+ let smallest = null;
193
+ for (const plans of chosen) {
194
+ const bytes = yield* write(plans);
195
+ if (!bytes) return smallest;
196
+ // Price the likely winner first, but retain rung 2's byte choice on an equal-length completed pair.
197
+ if (!smallest || bytes.length < smallest.length || (bytes.length === smallest.length && plans === candidates[0])) smallest = bytes;
198
+ if (hurried) return smallest;
199
+ }
200
+ return smallest;
201
+
202
+ // A plan's stream, a group per step; a hurry at its final group keeps the already-completed candidate.
203
+ function* write(plans) {
204
+ const {tree, leaves, freqs} = assemble(plans);
205
+ const transforms = channels >= 3 ? [{type: 'rct', beginC: 0, rctType: 6}] : [];
206
+ const header = new BitWriter(256);
207
+ writeImageHeader(header, width, height, shape.colour, shape.alpha, {colorSpace});
208
+ writeModularFrameHeader(header, {alpha: shape.alpha});
209
+ const global = new BitWriter(4096);
210
+ global.write(1, 1); // default DC quantisation
211
+ global.write(1, 1); // a global tree
212
+ const histograms = writeChannelHistograms(global, writeTree(global, tree), freqs, l => leaves.indexOf(l));
213
+ writeModularHeader(global, {useGlobalTree: true, transforms});
214
+ const codes = plans.map(plan => plan.leaves.map(l => histograms[leaves.indexOf(l) + 1].code));
215
+ const code = (w, c, width, height) => {
216
+ const plan = plans[c];
217
+ if (plan.leaves[0].predictor === WEIGHTED_PREDICTOR) codeWeighted(w, codes[c], planes[c], width, height, 0, plan.contextOf);
218
+ else codeChannel(w, codes[c][0], planes[c], width, height, plan.leaves[0]);
219
+ };
220
+ const sections = [];
221
+ if (layout.single) {
222
+ group(0, (w, h) => { for (let c = 0; c < channels; c++) code(global, c, w, h); });
223
+ hurried = yield next();
224
+ sections.push(global.finish());
225
+ } else {
226
+ sections.push(global.finish());
227
+ for (let i = 0; i < layout.dcGroupsX * layout.dcGroupsY + 1; i++) sections.push(new Uint8Array(0));
228
+ for (let g = 0; g < groups; g++) {
229
+ group(g, (w, h) => {
230
+ const section = new BitWriter(w * h * channels + 64);
231
+ writeModularHeader(section, {useGlobalTree: true, transforms: []});
232
+ for (let c = 0; c < channels; c++) code(section, c, w, h);
233
+ sections.push(section.finish());
234
+ });
235
+ if ((hurried = yield next()) && g + 1 < groups) return null;
236
+ }
237
+ }
238
+ return assembleCodestream(header, sections);
239
+ }
240
+ }
package/entropy.mjs CHANGED
@@ -45,10 +45,25 @@ export class TokenCounts {
45
45
  const slots = [0, 0, 0];
46
46
  function hybridTokenOf(config, value) { hybridToken(config, value, slots); return slots[0]; }
47
47
 
48
+ // log2 in arithmetic every engine rounds alike (Math.log2 does not): the exponent from the integer part's leading
49
+ // zeros and exact powers of two, then 2 atanh(z) / ln 2 as its series, z = (x - 1) / (x + 1) below 0.172 in magnitude.
50
+ const HALVES = [1];
51
+ for (let e = 1; e < 32; e++) HALVES.push(HALVES[e - 1] / 2);
52
+ function log2(x) {
53
+ let exponent = 0;
54
+ if (x >= 2 && x < 4294967296) { exponent = 31 - Math.clz32(x); x *= HALVES[exponent]; }
55
+ while (x >= 1.4142135623730951) { x /= 2; exponent++; }
56
+ while (x < 0.7071067811865476) { x *= 2; exponent--; }
57
+ const z = (x - 1) / (x + 1), z2 = z * z;
58
+ let term = z, sum = z;
59
+ for (let k = 3; k < 30; k += 2) sum += (term *= z2) / k;
60
+ return exponent + sum * 2.885390081777927;
61
+ }
62
+
48
63
  function entropyBits(freqs) {
49
64
  let total = 0, bits = 0;
50
65
  for (const f of freqs) total += f;
51
- for (const f of freqs) if (f) bits += f * Math.log2(total / f);
66
+ for (const f of freqs) if (f) bits += f * log2(total / f);
52
67
  return bits;
53
68
  }
54
69
 
package/frame.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  // Rapier's JPEG XL encoder: the codestream around a frame. MIT (LICENSE).
2
2
  // A bare codestream (no container): signature, size header, image metadata, one frame with its table of contents.
3
3
  import {BitWriter} from './bits.mjs';
4
+ import {admitOutputSize} from './admit.mjs';
4
5
 
5
6
  export const GROUP_DIM = 256, DC_GROUP_DIM = 2048;
6
7
 
@@ -8,8 +9,9 @@ function writeSize(w, size) {
8
9
  w.writeU32([[9, 1], [13, 1], [18, 1], [30, 1]], size);
9
10
  }
10
11
 
11
- // 8 bits per sample; `colour` 1 (grey) or 3 (sRGB); an 8-bit alpha channel when `alpha`. Frames start byte-aligned.
12
- export function writeImageHeader(w, width, height, colour, alpha, {xyb = false, orientation = 1} = {}) {
12
+ // 8 bits per sample; `colour` 1 (grey) or 3 (sRGB, or Display P3 when `colorSpace` says so); an 8-bit alpha channel
13
+ // when `alpha`. Frames start byte-aligned.
14
+ export function writeImageHeader(w, width, height, colour, alpha, {xyb = false, orientation = 1, colorSpace} = {}) {
13
15
  w.write(16, 0x0AFF);
14
16
  w.write(1, 0); // not the small size form
15
17
  writeSize(w, height);
@@ -23,7 +25,11 @@ export function writeImageHeader(w, width, height, colour, alpha, {xyb = false,
23
25
  if (alpha) w.write(3, 0b1_01); // one extra channel (2), all default (1): 8-bit alpha
24
26
  else w.write(2, 0);
25
27
  w.write(1, xyb ? 1 : 0); // xyb_encoded
26
- if (colour === 3) w.write(1, 1); // colour encoding all default: sRGB
28
+ if (colour === 3 && colorSpace === 'display-p3') {
29
+ // Display P3, fields in reverse order as below: not default (1), no ICC (1), RGB (2), D65 (2), the P3 primaries
30
+ // (selector 2 and 11 - 2 in 4), no gamma (1), the sRGB curve (selector 2 and 13 - 2 in 4), relative intent (2).
31
+ w.write(21, 0b01_1011_10_0_1001_10_01_00_0_0);
32
+ } else if (colour === 3) w.write(1, 1); // colour encoding all default: sRGB
27
33
  else {
28
34
  // Fixed grey encoding. Groups below are the fields in reverse order because the writer sends the low bits
29
35
  // first: not default (1), no ICC (1), grey (2), D65 (2), no gamma (1), sRGB selector (2) and enum (4), intent (2).
@@ -35,13 +41,14 @@ export function writeImageHeader(w, width, height, colour, alpha, {xyb = false,
35
41
  w.zeroPadToByte();
36
42
  }
37
43
 
38
- // A lossless modular frame, the last frame, no filters, one pass, 256-pixel groups, replace blending.
39
- export function writeModularFrameHeader(w, {alpha}) {
44
+ // A modular frame, the last frame, no filters, one pass, groups of 128 << shift pixels (256 unless asked), replace
45
+ // blending.
46
+ export function writeModularFrameHeader(w, {alpha, shift = 1}) {
40
47
  // Fixed beginning, low bits first: not all default (1), regular frame (2), modular (1), default flags (2),
41
48
  // not YCbCr (1), no upsampling (2).
42
49
  w.write(9, 0b00_0_00_1_00_0);
43
50
  if (alpha) w.write(2, 0); // no extra-channel upsampling
44
- w.write(2, 1); // group size shift 1: 256
51
+ w.write(2, shift); // the group size shift
45
52
  writeFrameHeaderEnd(w, alpha);
46
53
  }
47
54
 
@@ -61,9 +68,10 @@ export function writeTOC(w, sizes) {
61
68
  w.zeroPadToByte();
62
69
  }
63
70
 
64
- export function groupLayout(width, height) {
65
- const groupsX = Math.ceil(width / GROUP_DIM), groupsY = Math.ceil(height / GROUP_DIM);
66
- const dcGroupsX = Math.ceil(width / DC_GROUP_DIM), dcGroupsY = Math.ceil(height / DC_GROUP_DIM);
71
+ // The groups of a frame whose groups are `dim` pixels on a side, its DC groups eight times that.
72
+ export function groupLayout(width, height, dim = GROUP_DIM) {
73
+ const groupsX = Math.ceil(width / dim), groupsY = Math.ceil(height / dim);
74
+ const dcGroupsX = Math.ceil(width / (8 * dim)), dcGroupsY = Math.ceil(height / (8 * dim));
67
75
  return {groupsX, groupsY, dcGroupsX, dcGroupsY, single: groupsX === 1 && groupsY === 1};
68
76
  }
69
77
 
@@ -77,7 +85,9 @@ export function assembleCodestream(header, sections) {
77
85
  const sizes = sections.map(section => section.length);
78
86
  writeTOC(header, sizes);
79
87
  const head = header.finish();
80
- const out = new Uint8Array(head.length + sizes.reduce((a, b) => a + b, 0));
88
+ const length = head.length + sizes.reduce((a, b) => a + b, 0);
89
+ admitOutputSize(length);
90
+ const out = new Uint8Array(length);
81
91
  out.set(head);
82
92
  let at = head.length;
83
93
  for (const section of sections) { out.set(section, at); at += section.length; }
package/index.d.mts CHANGED
@@ -1,27 +1,23 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // Rapier JXL: the types of the checked entries, the limits and the error codes. The raw entries beneath the
3
- // checked ones take input the checked entries have already admitted; their shapes are the modules' own.
2
+ // Rapier JXL's core: the type of encode, the shape of the limits (each door has its own, by its memory) and the error
3
+ // codes every door shares.
4
4
  export type Pixels = Uint8Array | Uint8ClampedArray;
5
5
  export interface Limits { readonly bytes: number; readonly pixels: number; readonly edge: number; }
6
- /** The 16 MiB codestream, 24 million pixels, 16,384 on a side; a larger ask is refused before any work. */
6
+ /** The core's limits: the 16 MiB codestream, 24 million pixels, 16,384 on a side; a larger ask is refused before any
7
+ * work. The photo door takes 40 million pixels and the JPEG carrier 64 million, each by its memory. */
7
8
  export const LIMITS: Limits;
8
- export interface EncodeOptions { /** 1 to 100; 100 (the default) is exact. */ quality?: number; }
9
- export interface Transcoded {
10
- /** A bare JPEG XL codestream, the .jxl file's bytes. */ bytes: Uint8Array;
11
- /** The picture's size as shown, swapped when the Exif orientation turns it. */ width: number; height: number;
12
- /** The Exif orientation, 1 to 8, kept in the JPEG XL header. */ orientation: number;
9
+ export interface EncodeOptions {
10
+ /** 1 to 100; 100 is exact. */ quality?: number;
11
+ /** The samples' colour space, declared in the header; 'srgb' by default. */ colorSpace?: 'srgb' | 'display-p3';
12
+ /** 1 to 9, a whole number, read by /effort, which runs its highest rung at or below it; the core has rung 1 alone. */ effort?: number;
13
13
  }
14
14
  export type ErrorCode = 'JXL_INPUT' | 'JXL_DIMENSIONS' | 'JXL_SIZE' | 'JXL_MEMORY' | 'JXL_JPEG';
15
15
  /** Every refusal is an Error carrying one of the five codes. */
16
16
  export interface EncoderError extends Error { code: ErrorCode; }
17
17
  /** Straight RGBA bytes, row by row, to a bare JPEG XL codestream; quality 100 (the default) is exact. */
18
18
  export function encode(data: Pixels, width: number, height: number, options?: EncodeOptions): Uint8Array;
19
- export function encodeLosslessRGBA(data: Pixels, width: number, height: number): Uint8Array;
20
- export function encodeLossyRGBA(data: Pixels, width: number, height: number, quality?: number): Uint8Array;
21
- /** A JPEG's bytes to JPEG XL, its coefficients carried; a JPEG this path does not take is refused as JXL_JPEG. */
22
- export function transcode(jpeg: Uint8Array): Transcoded;
23
- export function encodeLossless(data: Pixels, width: number, height: number, options?: {shape?: unknown}): Uint8Array;
24
- export function encodeLossy(data: Pixels, width: number, height: number, options: {quality: number; shape?: unknown}): Uint8Array;
25
- export function transcodeJPEG(jpeg: Uint8Array, parsed: unknown): Uint8Array;
26
- export function parseJPEG(jpeg: Uint8Array): unknown;
27
- export function inspectPixels(data: Pixels, width: number, height: number): unknown;
19
+ /** A door's work as steps: iterate for the fraction done, in (0, 1], the last exactly 1; `bytes` holds the stream after
20
+ * the last step. Leaving the loop cancels. `hurry` asks a search to finish with the best candidate it has priced. */
21
+ export interface Job extends Generator<number, Uint8Array, undefined> { bytes: Uint8Array | null; hurry: boolean; }
22
+ /** encode's work and bytes, a group of one pass per step; the call is admitted at once. */
23
+ export function encodeSteps(data: Pixels, width: number, height: number, options?: EncodeOptions): Job;