pica
Version:
High quality image resize in browser.
1 lines • 115 kB
Source Map (JSON)
{"version":3,"file":"pica.mjs","names":[],"sources":["../../multimath/lib/base64decode.mjs","../../multimath/lib/wa_detect.mjs","../../multimath/lib/multimath.mjs","../node_modules/glur/lib/blur_mono16.mjs","../src/mm_unsharp_mask/unsharp_mask.ts","../src/mm_unsharp_mask/unsharp_mask_wasm.ts","../src/mm_unsharp_mask/unsharp_mask_wasm_base64.ts","../src/mm_unsharp_mask/index.ts","../src/mm_resize/resize_filter_info.ts","../src/mm_resize/resize_filter_gen.ts","../src/mm_resize/convolve.ts","../src/mm_resize/resize.ts","../src/mm_resize/resize_wasm.ts","../src/mm_resize/convolve_wasm_base64.ts","../src/mm_resize/index.ts","../src/mathlib.ts","../src/pool.ts","../src/utils.ts","../src/stepper.ts","../src/tiler.ts","../src/supported_features.ts","../src/pica_main.ts"],"sourcesContent":["// base64 decode str -> Uint8Array, to load WA modules\n//\nconst BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\n\nexport default function base64decode (str) {\n const input = str.replace(/[\\r\\n=]/g, ''), // remove CR/LF & padding to simplify scan\n max = input.length\n\n const out = new Uint8Array((max * 3) >> 2)\n\n // Collect by 6*4 bits (3 bytes)\n\n let bits = 0\n let ptr = 0\n\n for (let idx = 0; idx < max; idx++) {\n if ((idx % 4 === 0) && idx) {\n out[ptr++] = (bits >> 16) & 0xFF\n out[ptr++] = (bits >> 8) & 0xFF\n out[ptr++] = bits & 0xFF\n }\n\n bits = (bits << 6) | BASE64_MAP.indexOf(input.charAt(idx))\n }\n\n // Dump tail\n\n const tailbits = (max % 4) * 6\n\n if (tailbits === 0) {\n out[ptr++] = (bits >> 16) & 0xFF\n out[ptr++] = (bits >> 8) & 0xFF\n out[ptr++] = bits & 0xFF\n } else if (tailbits === 18) {\n out[ptr++] = (bits >> 10) & 0xFF\n out[ptr++] = (bits >> 2) & 0xFF\n } else if (tailbits === 12) {\n out[ptr++] = (bits >> 4) & 0xFF\n }\n\n return out\n}\n","// Detect WebAssembly support.\n// - Check global WebAssembly object\n// - Try to load simple module (can be disabled via CSP)\n//\nlet wa\n\nexport default function hasWebAssembly () {\n // use cache if called before;\n if (typeof wa !== 'undefined') return wa\n\n wa = false\n\n if (typeof WebAssembly === 'undefined') return wa\n\n // If WebAssenbly is disabled, code can throw on compile\n try {\n // https://github.com/brion/min-wasm-fail/blob/master/min-wasm-fail.in.js\n // Additional check that WA internals are correct\n\n const bin = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127, 3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 16, 1, 14, 0, 32, 0, 65, 1, 54, 2, 0, 32, 0, 40, 2, 0, 11])\n const module = new WebAssembly.Module(bin)\n const instance = new WebAssembly.Instance(module, {})\n\n // test storing to and loading from a non-zero location via a parameter.\n // Safari on iOS 11.2.5 returns 0 unexpectedly at non-zero locations\n if (instance.exports.test(4) !== 0) wa = true\n\n return wa\n } catch (__) {}\n\n return wa\n}\n","import base64decode from './base64decode.mjs'\nimport hasWebAssembly from './wa_detect.mjs'\n\nconst DEFAULT_OPTIONS = {\n js: true,\n wasm: true\n}\n\nclass MultiMath {\n constructor (options) {\n const opts = Object.assign({}, DEFAULT_OPTIONS, options || {})\n\n this.options = opts\n\n this.__cache = {}\n\n this.__init_promise = null\n this.__modules = opts.modules || {}\n this.__memory = null\n this.__wasm = {}\n\n this.__isLE = ((new Uint32Array((new Uint8Array([1, 0, 0, 0])).buffer))[0] === 1)\n\n if (!this.options.js && !this.options.wasm) {\n throw new Error('mathlib: at least \"js\" or \"wasm\" should be enabled')\n }\n }\n\n has_wasm () { return hasWebAssembly() }\n\n use (module) {\n this.__modules[module.name] = module\n\n // Pin the best possible implementation\n if (this.options.wasm && this.has_wasm() && module.wasm_fn) {\n this[module.name] = module.wasm_fn\n } else {\n this[module.name] = module.fn\n }\n\n return this\n }\n\n init () {\n if (this.__init_promise) return this.__init_promise\n\n if (!this.options.js && this.options.wasm && !this.has_wasm()) {\n return Promise.reject(new Error('mathlib: only \"wasm\" was enabled, but it\\'s not supported'))\n }\n\n this.__init_promise = Promise.all(Object.keys(this.__modules).map(name => {\n const module = this.__modules[name]\n\n if (!this.options.wasm || !this.has_wasm() || !module.wasm_fn) return null\n\n // If already compiled - exit\n if (this.__wasm[name]) return null\n\n // Compile wasm source\n return WebAssembly.compile(base64decode(module.wasm_src))\n .then(m => { this.__wasm[name] = m })\n }))\n .then(() => this)\n\n return this.__init_promise\n }\n\n // //////////////////////////////////////////////////////////////////////////////\n // Methods below are for internal use from plugins\n\n // Increase current memory to include specified number of bytes. Do nothing if\n // size is already ok. You probably don't need to call this method directly,\n // because it will be invoked from `.__instance()`.\n //\n __reallocate (bytes) {\n if (!this.__memory) {\n this.__memory = new WebAssembly.Memory({\n initial: Math.ceil(bytes / (64 * 1024))\n })\n return this.__memory\n }\n\n const mem_size = this.__memory.buffer.byteLength\n\n if (mem_size < bytes) {\n this.__memory.grow(Math.ceil((bytes - mem_size) / (64 * 1024)))\n }\n\n return this.__memory\n }\n\n // Returns instantinated webassembly item by name, with specified memory size\n // and environment.\n // - use cache if available\n // - do sync module init, if async init was not called earlier\n // - allocate memory if not enougth\n // - can export functions to webassembly via \"env_extra\",\n // for example, { exp: Math.exp }\n //\n __instance (name, memsize, env_extra) {\n if (memsize) this.__reallocate(memsize)\n\n // If .init() was not called, do sync compile\n if (!this.__wasm[name]) {\n const module = this.__modules[name]\n this.__wasm[name] = new WebAssembly.Module(base64decode(module.wasm_src))\n }\n\n if (!this.__cache[name]) {\n const env_base = {\n memoryBase: 0,\n memory: this.__memory,\n tableBase: 0,\n table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })\n }\n\n this.__cache[name] = new WebAssembly.Instance(this.__wasm[name], {\n env: Object.assign(env_base, env_extra || {})\n })\n }\n\n return this.__cache[name]\n }\n\n // Helper to calculate memory aligh for pointers. Webassembly does not require\n // this, but you may wish to experiment. Default base = 8;\n //\n __align (number, base) {\n base = base || 8\n const reminder = number % base\n return number + (reminder ? base - reminder : 0)\n }\n}\n\nexport default MultiMath\n","// Calculate Gaussian blur of an image using IIR filter\n// The method is taken from Intel's white paper and code example attached to it:\n// https://software.intel.com/en-us/articles/iir-gaussian-blur-filter\n// -implementation-using-intel-advanced-vector-extensions\n\nfunction gaussCoef (sigma) {\n if (sigma < 0.5) {\n sigma = 0.5\n }\n\n const a = Math.exp(0.726 * 0.726) / sigma,\n g1 = Math.exp(-a),\n g2 = Math.exp(-2 * a),\n k = (1 - g1) * (1 - g1) / (1 + 2 * a * g1 - g2)\n\n const a0 = k\n const a1 = k * (a - 1) * g1\n const a2 = k * (a + 1) * g1\n const a3 = -k * g2\n const b1 = 2 * g1\n const b2 = -g2\n const left_corner = (a0 + a1) / (1 - b1 - b2)\n const right_corner = (a2 + a3) / (1 - b1 - b2)\n\n // Attempt to force type to FP32.\n return new Float32Array([a0, a1, a2, a3, b1, b2, left_corner, right_corner])\n}\n\nfunction convolveMono16 (src, out, line, coeff, width, height) {\n // takes src image and writes the blurred and transposed result into out\n\n let prev_src, curr_src, curr_out, prev_out, prev_prev_out\n let src_index, out_index, line_index\n let i, j\n let coeff_a0, coeff_a1, coeff_b1, coeff_b2\n\n for (i = 0; i < height; i++) {\n src_index = i * width\n out_index = i\n line_index = 0\n\n // left to right\n prev_src = src[src_index]\n prev_prev_out = prev_src * coeff[6]\n prev_out = prev_prev_out\n\n coeff_a0 = coeff[0]\n coeff_a1 = coeff[1]\n coeff_b1 = coeff[4]\n coeff_b2 = coeff[5]\n\n for (j = 0; j < width; j++) {\n curr_src = src[src_index]\n\n curr_out = curr_src * coeff_a0 +\n prev_src * coeff_a1 +\n prev_out * coeff_b1 +\n prev_prev_out * coeff_b2\n\n prev_prev_out = prev_out\n prev_out = curr_out\n prev_src = curr_src\n\n line[line_index] = prev_out\n line_index++\n src_index++\n }\n\n src_index--\n line_index--\n out_index += height * (width - 1)\n\n // right to left\n prev_src = src[src_index]\n prev_prev_out = prev_src * coeff[7]\n prev_out = prev_prev_out\n curr_src = prev_src\n\n coeff_a0 = coeff[2]\n coeff_a1 = coeff[3]\n\n for (j = width - 1; j >= 0; j--) {\n curr_out = curr_src * coeff_a0 +\n prev_src * coeff_a1 +\n prev_out * coeff_b1 +\n prev_prev_out * coeff_b2\n\n prev_prev_out = prev_out\n prev_out = curr_out\n\n prev_src = curr_src\n curr_src = src[src_index]\n\n out[out_index] = line[line_index] + prev_out\n\n src_index--\n line_index--\n out_index -= height\n }\n }\n}\n\nfunction blurMono16 (src, width, height, radius) {\n // Quick exit on zero radius\n if (!radius) { return }\n\n const out = new Uint16Array(src.length),\n tmp_line = new Float32Array(Math.max(width, height))\n\n const coeff = gaussCoef(radius)\n\n convolveMono16(src, out, tmp_line, coeff, width, height, radius)\n convolveMono16(out, src, tmp_line, coeff, height, width, radius)\n}\n\nexport default blurMono16\n","// Unsharp mask filter\n//\n// http://stackoverflow.com/a/23322820/1031804\n// USM(O) = O + (2 * (Amount / 100) * (O - GB))\n// GB - gaussian blur.\n//\n// Image is converted from RGB to HSV, unsharp mask is applied to the\n// brightness channel and then image is converted back to RGB.\n//\nimport { blurMono16 } from 'glur'\nimport type { MathImageBuffer } from '../mathlib'\n\nfunction hsv_v16 (img: MathImageBuffer, width: number, height: number): Uint16Array {\n const size = width * height\n const out = new Uint16Array(size)\n let r, g, b, max\n for (let i = 0; i < size; i++) {\n r = img[4 * i]\n g = img[4 * i + 1]\n b = img[4 * i + 2]\n max = (r >= g && r >= b) ? r : (g >= b && g >= r) ? g : b\n out[i] = max << 8\n }\n return out\n}\n\nexport default function unsharp (\n img: MathImageBuffer,\n width: number,\n height: number,\n amount: number,\n radius: number,\n threshold: number\n): void {\n let v1, v2, vmul\n let diff, iTimes4\n\n if (amount === 0 || radius < 0.5) {\n return\n }\n if (radius > 2.0) {\n radius = 2.0\n }\n\n const brightness = hsv_v16(img, width, height)\n\n const blured = new Uint16Array(brightness) // copy, because blur modifies src\n\n blurMono16(blured, width, height, radius)\n\n const amountFp = (amount / 100 * 0x1000 + 0.5)|0\n const thresholdFp = threshold << 8\n\n const size = width * height\n\n for (let i = 0; i < size; i++) {\n v1 = brightness[i]\n diff = v1 - blured[i]\n\n if (Math.abs(diff) >= thresholdFp) {\n // add unsharp mask to the brightness channel\n v2 = v1 + ((amountFp * diff + 0x800) >> 12)\n\n // Both v1 and v2 are within [0.0 .. 255.0] (0000-FF00) range, never going into\n // [255.003 .. 255.996] (FF01-FFFF). This allows to round this value as (x+.5)|0\n // later without overflowing.\n v2 = v2 > 0xff00 ? 0xff00 : v2\n v2 = v2 < 0x0000 ? 0x0000 : v2\n\n // Avoid division by 0. V=0 means rgb(0,0,0), unsharp with unsharpAmount>0 cannot\n // change this value (because diff between colors gets inflated), so no need to verify correctness.\n v1 = v1 !== 0 ? v1 : 1\n\n // Multiplying V in HSV model by a constant is equivalent to multiplying each component\n // in RGB by the same constant (same for HSL), see also:\n // https://beesbuzz.biz/code/16-hsv-color-transforms\n vmul = ((v2 << 12) / v1)|0\n\n // Result will be in [0..255] range because:\n // - all numbers are positive\n // - r,g,b <= (v1/256)\n // - r,g,b,(v1/256),(v2/256) <= 255\n // So highest this number can get is X*255/X+0.5=255.5 which is < 256 and rounds down.\n\n iTimes4 = i * 4\n img[iTimes4] = (img[iTimes4] * vmul + 0x800) >> 12 // R\n img[iTimes4 + 1] = (img[iTimes4 + 1] * vmul + 0x800) >> 12 // G\n img[iTimes4 + 2] = (img[iTimes4 + 2] * vmul + 0x800) >> 12 // B\n }\n }\n}\n","import type { MathWasmContext, MathImageBuffer } from '../mathlib'\n\nexport default function unsharp (\n this: MathWasmContext,\n img: MathImageBuffer,\n width: number,\n height: number,\n amount: number,\n radius: number,\n threshold: number\n): void {\n if (amount === 0 || radius < 0.5) {\n return\n }\n\n if (radius > 2.0) {\n radius = 2.0\n }\n\n const pixels = width * height\n\n const img_bytes_cnt = pixels * 4\n const hsv_bytes_cnt = pixels * 2\n const blur_bytes_cnt = pixels * 2\n const blur_line_byte_cnt = Math.max(width, height) * 4 // float32 array\n const blur_coeffs_byte_cnt = 8 * 4 // float32 array\n\n const img_offset = 0\n const hsv_offset = img_bytes_cnt\n const blur_offset = hsv_offset + hsv_bytes_cnt\n const blur_tmp_offset = blur_offset + blur_bytes_cnt\n const blur_line_offset = blur_tmp_offset + blur_bytes_cnt\n const blur_coeffs_offset = blur_line_offset + blur_line_byte_cnt\n\n const instance = this.__instance(\n 'unsharp_mask',\n img_bytes_cnt + hsv_bytes_cnt + blur_bytes_cnt * 2 + blur_line_byte_cnt + blur_coeffs_byte_cnt,\n { exp: Math.exp }\n )\n\n // 32-bit copy is much faster in chrome\n const img32 = new Uint32Array(img.buffer)\n const mem32 = new Uint32Array(this.__memory!.buffer)\n mem32.set(img32)\n\n // HSL\n let fn = (instance.exports.hsv_v16 || instance.exports._hsv_v16) as Function | undefined\n if (!fn) throw new Error('WASM hsv_v16 function is not available')\n fn(img_offset, hsv_offset, width, height)\n\n // BLUR\n fn = (instance.exports.blurMono16 || instance.exports._blurMono16) as Function | undefined\n if (!fn) throw new Error('WASM blurMono16 function is not available')\n fn(hsv_offset, blur_offset, blur_tmp_offset,\n blur_line_offset, blur_coeffs_offset, width, height, radius)\n\n // UNSHARP\n fn = (instance.exports.unsharp || instance.exports._unsharp) as Function | undefined\n if (!fn) throw new Error('WASM unsharp function is not available')\n fn(img_offset, img_offset, hsv_offset,\n blur_offset, width, height, amount, threshold)\n\n // 32-bit copy is much faster in chrome\n img32.set(new Uint32Array(this.__memory!.buffer, 0, pixels))\n}\n","// This is autogenerated file from math.wasm, don't edit.\n//\nexport default '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'\n","import fn from './unsharp_mask'\nimport wasm_fn from './unsharp_mask_wasm'\nimport wasm_src from './unsharp_mask_wasm_base64'\n\nexport default {\n name: 'unsharp_mask' as const,\n fn,\n wasm_fn,\n wasm_src\n}\n","import type { MathResizeFilter } from '../mathlib'\n\n// Filter definitions to build tables for\n// resizing convolvers.\n//\n// Presets for quality 0..3. Filter functions + window size\n//\nexport interface FilterInfo {\n win: number\n fn: (x: number) => number\n}\n\nconst filter: Record<MathResizeFilter, FilterInfo> = {\n // Nearest neighbor\n box: {\n win: 0.5,\n fn (x: number) {\n if (x < 0) x = -x\n return (x < 0.5) ? 1.0 : 0.0\n }\n },\n // // Hamming\n hamming: {\n win: 1.0,\n fn (x: number) {\n if (x < 0) x = -x\n if (x >= 1.0) { return 0.0 }\n if (x < 1.19209290E-07) { return 1.0 }\n const xpi = x * Math.PI\n return ((Math.sin(xpi) / xpi) * (0.54 + 0.46 * Math.cos(xpi / 1.0)))\n }\n },\n // Lanczos, win = 2\n lanczos2: {\n win: 2.0,\n fn (x: number) {\n if (x < 0) x = -x\n if (x >= 2.0) { return 0.0 }\n if (x < 1.19209290E-07) { return 1.0 }\n const xpi = x * Math.PI\n return (Math.sin(xpi) / xpi) * Math.sin(xpi / 2.0) / (xpi / 2.0)\n }\n },\n // Lanczos, win = 3\n lanczos3: {\n win: 3.0,\n fn (x: number) {\n if (x < 0) x = -x\n if (x >= 3.0) { return 0.0 }\n if (x < 1.19209290E-07) { return 1.0 }\n const xpi = x * Math.PI\n return (Math.sin(xpi) / xpi) * Math.sin(xpi / 3.0) / (xpi / 3.0)\n }\n },\n // Magic Kernel Sharp 2013, win = 2.5\n // http://johncostella.com/magic/\n mks2013: {\n win: 2.5,\n fn (x: number) {\n if (x < 0) x = -x\n if (x >= 2.5) { return 0.0 }\n if (x >= 1.5) { return -0.125 * (x - 2.5) * (x - 2.5) }\n if (x >= 0.5) { return 0.25 * (4 * x * x - 11 * x + 7) }\n return 1.0625 - 1.75 * x * x\n }\n }\n}\n\nexport default {\n filter\n}\n","// Calculate convolution filters for each destination point,\n// and pack data to Int16Array:\n//\n// [ shift, length, data..., shift2, length2, data..., ... ]\n//\n// - shift - offset in src image\n// - length - filter length (in src points)\n// - data - filter values sequence\n//\nimport FILTER_INFO from './resize_filter_info'\nimport type { MathResizeFilter } from '../mathlib'\n\n// Precision of fixed FP values\nconst FIXED_FRAC_BITS = 14\n\nfunction toFixedPoint (num: number): number {\n return Math.round(num * ((1 << FIXED_FRAC_BITS) - 1))\n}\n\nexport default function resizeFilterGen (\n filter: MathResizeFilter,\n srcSize: number,\n destSize: number,\n scale: number,\n offset: number\n): Int16Array {\n const filterFunction = FILTER_INFO.filter[filter].fn\n\n const scaleInverted = 1.0 / scale\n const scaleClamped = Math.min(1.0, scale) // For upscale\n\n // Filter window (averaging interval), scaled to src image\n const srcWindow = FILTER_INFO.filter[filter].win / scaleClamped\n\n let destPixel, srcPixel, srcFirst, srcLast, filterElementSize,\n floatFilter, fxpFilter, total, pxl, idx, floatVal, filterTotal, filterVal\n let leftNotEmpty, rightNotEmpty, filterShift, filterSize\n\n const maxFilterElementSize = Math.floor((srcWindow + 1) * 2)\n const packedFilter = new Int16Array((maxFilterElementSize + 2) * destSize)\n let packedFilterPtr = 0\n\n const slowCopy = !packedFilter.subarray || !packedFilter.set\n\n // For each destination pixel calculate source range and built filter values\n for (destPixel = 0; destPixel < destSize; destPixel++) {\n // Scaling should be done relative to central pixel point\n srcPixel = (destPixel + 0.5) * scaleInverted + offset\n\n srcFirst = Math.max(0, Math.floor(srcPixel - srcWindow))\n srcLast = Math.min(srcSize - 1, Math.ceil(srcPixel + srcWindow))\n\n filterElementSize = srcLast - srcFirst + 1\n floatFilter = new Float32Array(filterElementSize)\n fxpFilter = new Int16Array(filterElementSize)\n\n total = 0.0\n\n // Fill filter values for calculated range\n for (pxl = srcFirst, idx = 0; pxl <= srcLast; pxl++, idx++) {\n floatVal = filterFunction(((pxl + 0.5) - srcPixel) * scaleClamped)\n total += floatVal\n floatFilter[idx] = floatVal\n }\n\n // Normalize filter, convert to fixed point and accumulate conversion error\n filterTotal = 0\n\n for (idx = 0; idx < floatFilter.length; idx++) {\n filterVal = floatFilter[idx] / total\n filterTotal += filterVal\n fxpFilter[idx] = toFixedPoint(filterVal)\n }\n\n // Compensate normalization error, to minimize brightness drift\n fxpFilter[destSize >> 1] += toFixedPoint(1.0 - filterTotal)\n\n //\n // Now pack filter to useable form\n //\n // 1. Trim leading and trailing zero values, and compensate shift/length\n // 2. Put all to single array in this format:\n //\n // [ pos shift, data length, value1, value2, value3, ... ]\n //\n\n leftNotEmpty = 0\n while (leftNotEmpty < fxpFilter.length && fxpFilter[leftNotEmpty] === 0) {\n leftNotEmpty++\n }\n\n if (leftNotEmpty < fxpFilter.length) {\n rightNotEmpty = fxpFilter.length - 1\n while (rightNotEmpty > 0 && fxpFilter[rightNotEmpty] === 0) {\n rightNotEmpty--\n }\n\n filterShift = srcFirst + leftNotEmpty\n filterSize = rightNotEmpty - leftNotEmpty + 1\n\n packedFilter[packedFilterPtr++] = filterShift // shift\n packedFilter[packedFilterPtr++] = filterSize // size\n\n if (!slowCopy) {\n packedFilter.set(fxpFilter.subarray(leftNotEmpty, rightNotEmpty + 1), packedFilterPtr)\n packedFilterPtr += filterSize\n } else {\n // fallback for old IE < 11, without subarray/set methods\n for (idx = leftNotEmpty; idx <= rightNotEmpty; idx++) {\n packedFilter[packedFilterPtr++] = fxpFilter[idx]\n }\n }\n } else {\n // zero data, write header only\n packedFilter[packedFilterPtr++] = 0 // shift\n packedFilter[packedFilterPtr++] = 0 // size\n }\n }\n return packedFilter\n}\n","// Resize convolvers, pure JS implementation\n//\n// Precision of fixed FP values\n// var FIXED_FRAC_BITS = 14;\n\nfunction clampTo8 (i: number): number { return i < 0 ? 0 : (i > 255 ? 255 : i) }\nfunction clampNegative (i: number): number { return i >= 0 ? i : 0 }\n\nimport type { MathImageBuffer } from '../mathlib'\n\n// Convolve image data in horizontal direction. Can be used for:\n//\n// 1. bitmap with premultiplied alpha\n// 2. bitmap without alpha (all values 255)\n//\n// Notes:\n//\n// - output is transposed\n// - output resolution is ~15 bits per channel(for better precision).\n//\nfunction convolveHor (\n src: MathImageBuffer,\n dest: Uint16Array,\n srcW: number,\n srcH: number,\n destW: number,\n filters: Int16Array\n): void {\n let r, g, b, a\n let filterPtr, filterShift, filterSize\n let srcPtr, srcY, destX, filterVal\n let srcOffset = 0, destOffset = 0\n\n // For each row\n for (srcY = 0; srcY < srcH; srcY++) {\n filterPtr = 0\n\n // Apply precomputed filters to each destination row point\n for (destX = 0; destX < destW; destX++) {\n // Get the filter that determines the current output pixel.\n filterShift = filters[filterPtr++]\n filterSize = filters[filterPtr++]\n\n srcPtr = (srcOffset + (filterShift * 4))|0\n\n r = g = b = a = 0\n\n // Apply the filter to the row to get the destination pixel r, g, b, a\n for (; filterSize > 0; filterSize--) {\n filterVal = filters[filterPtr++]\n\n // Use reverse order to workaround deopts in old v8 (node v.10)\n // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.\n a = (a + filterVal * src[srcPtr + 3])|0\n b = (b + filterVal * src[srcPtr + 2])|0\n g = (g + filterVal * src[srcPtr + 1])|0\n r = (r + filterVal * src[srcPtr])|0\n srcPtr = (srcPtr + 4)|0\n }\n\n // Store 15 bits between passes for better precision\n // Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only\n //\n dest[destOffset + 3] = clampNegative(a >> 7)\n dest[destOffset + 2] = clampNegative(b >> 7)\n dest[destOffset + 1] = clampNegative(g >> 7)\n dest[destOffset] = clampNegative(r >> 7)\n destOffset = (destOffset + srcH * 4)|0\n }\n\n destOffset = ((srcY + 1) * 4)|0\n srcOffset = ((srcY + 1) * srcW * 4)|0\n }\n}\n\n// Supplementary method for `convolveHor()`\n//\nfunction convolveVert (\n src: Uint16Array,\n dest: Uint8Array,\n srcW: number,\n srcH: number,\n destW: number,\n filters: Int16Array\n): void {\n let r, g, b, a\n let filterPtr, filterShift, filterSize\n let srcPtr, srcY, destX, filterVal\n let srcOffset = 0, destOffset = 0\n\n // For each row\n for (srcY = 0; srcY < srcH; srcY++) {\n filterPtr = 0\n\n // Apply precomputed filters to each destination row point\n for (destX = 0; destX < destW; destX++) {\n // Get the filter that determines the current output pixel.\n filterShift = filters[filterPtr++]\n filterSize = filters[filterPtr++]\n\n srcPtr = (srcOffset + (filterShift * 4))|0\n\n r = g = b = a = 0\n\n // Apply the filter to the row to get the destination pixel r, g, b, a\n for (; filterSize > 0; filterSize--) {\n filterVal = filters[filterPtr++]\n\n // Use reverse order to workaround deopts in old v8 (node v.10)\n // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.\n a = (a + filterVal * src[srcPtr + 3])|0\n b = (b + filterVal * src[srcPtr + 2])|0\n g = (g + filterVal * src[srcPtr + 1])|0\n r = (r + filterVal * src[srcPtr])|0\n srcPtr = (srcPtr + 4)|0\n }\n\n // Sync with premultiplied version for exact result match\n r >>= 7\n g >>= 7\n b >>= 7\n a >>= 7\n\n // Bring this value back in range + round result.\n //\n dest[destOffset + 3] = clampTo8((a + (1 << 13)) >> 14)\n dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14)\n dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14)\n dest[destOffset] = clampTo8((r + (1 << 13)) >> 14)\n destOffset = (destOffset + srcH * 4)|0\n }\n\n destOffset = ((srcY + 1) * 4)|0\n srcOffset = ((srcY + 1) * srcW * 4)|0\n }\n}\n\n// Premultiply & convolve image data in horizontal direction. Can be used for:\n//\n// - Any bitmap data, extracted with `.getImageData()` method (with\n// non-premultiplied alpha)\n//\n// For images without alpha channel this method is slower than `convolveHor()`\n//\nfunction convolveHorWithPre (\n src: MathImageBuffer,\n dest: Uint16Array,\n srcW: number,\n srcH: number,\n destW: number,\n filters: Int16Array\n): void {\n let r, g, b, a, alpha\n let filterPtr, filterShift, filterSize\n let srcPtr, srcY, destX, filterVal\n let srcOffset = 0, destOffset = 0\n\n // For each row\n for (srcY = 0; srcY < srcH; srcY++) {\n filterPtr = 0\n\n // Apply precomputed filters to each destination row point\n for (destX = 0; destX < destW; destX++) {\n // Get the filter that determines the current output pixel.\n filterShift = filters[filterPtr++]\n filterSize = filters[filterPtr++]\n\n srcPtr = (srcOffset + (filterShift * 4))|0\n\n r = g = b = a = 0\n\n // Apply the filter to the row to get the destination pixel r, g, b, a\n for (; filterSize > 0; filterSize--) {\n filterVal = filters[filterPtr++]\n\n // Use reverse order to workaround deopts in old v8 (node v.10)\n // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.\n alpha = src[srcPtr + 3]\n a = (a + filterVal * alpha)|0\n b = (b + filterVal * src[srcPtr + 2] * alpha)|0\n g = (g + filterVal * src[srcPtr + 1] * alpha)|0\n r = (r + filterVal * src[srcPtr] * alpha)|0\n srcPtr = (srcPtr + 4)|0\n }\n\n // Premultiply is (* alpha / 255).\n // Postpone division for better performance\n b = (b / 255)|0\n g = (g / 255)|0\n r = (r / 255)|0\n\n // Store 15 bits between passes for better precision\n // Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only\n //\n dest[destOffset + 3] = clampNegative(a >> 7)\n dest[destOffset + 2] = clampNegative(b >> 7)\n dest[destOffset + 1] = clampNegative(g >> 7)\n dest[destOffset] = clampNegative(r >> 7)\n destOffset = (destOffset + srcH * 4)|0\n }\n\n destOffset = ((srcY + 1) * 4)|0\n srcOffset = ((srcY + 1) * srcW * 4)|0\n }\n}\n\n// Supplementary method for `convolveHorWithPre()`\n//\nfunction convolveVertWithPre (\n src: Uint16Array,\n dest: Uint8Array,\n srcW: number,\n srcH: number,\n destW: number,\n filters: Int16Array\n): void {\n let r, g, b, a\n let filterPtr, filterShift, filterSize\n let srcPtr, srcY, destX, filterVal\n let srcOffset = 0, destOffset = 0\n\n // For each row\n for (srcY = 0; srcY < srcH; srcY++) {\n filterPtr = 0\n\n // Apply precomputed filters to each destination row point\n for (destX = 0; destX < destW; destX++) {\n // Get the filter that determines the current output pixel.\n filterShift = filters[filterPtr++]\n filterSize = filters[filterPtr++]\n\n srcPtr = (srcOffset + (filterShift * 4))|0\n\n r = g = b = a = 0\n\n // Apply the filter to the row to get the destination pixel r, g, b, a\n for (; filterSize > 0; filterSize--) {\n filterVal = filters[filterPtr++]\n\n // Use reverse order to workaround deopts in old v8 (node v.10)\n // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.\n a = (a + filterVal * src[srcPtr + 3])|0\n b = (b + filterVal * src[srcPtr + 2])|0\n g = (g + filterVal * src[srcPtr + 1])|0\n r = (r + filterVal * src[srcPtr])|0\n srcPtr = (srcPtr + 4)|0\n }\n\n // Downscale to leave room for un-premultiply\n r >>= 7\n g >>= 7\n b >>= 7\n a >>= 7\n\n // Un-premultiply\n a = clampTo8((a + (1 << 13)) >> 14)\n if (a > 0) {\n r = (r * 255 / a)|0\n g = (g * 255 / a)|0\n b = (b * 255 / a)|0\n }\n\n // Bring this value back in range + round result.\n // Shift value = FIXED_FRAC_BITS + 7\n //\n dest[destOffset + 3] = a\n dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14)\n dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14)\n dest[destOffset] = clampTo8((r + (1 << 13)) >> 14)\n destOffset = (destOffset + srcH * 4)|0\n }\n\n destOffset = ((srcY + 1) * 4)|0\n srcOffset = ((srcY + 1) * srcW * 4)|0\n }\n}\n\nexport {\n convolveHor,\n convolveVert,\n convolveHorWithPre,\n convolveVertWithPre\n}\n","import createFilters from './resize_filter_gen'\nimport { convolveHor, convolveVert, convolveHorWithPre, convolveVertWithPre } from './convolve'\nimport type { MathResizeOptions, MathImageBuffer } from '../mathlib'\n\nfunction hasAlpha (src: MathImageBuffer, width: number, height: number): boolean {\n let ptr = 3\n const len = (width * height * 4)|0\n while (ptr < len) {\n if (src[ptr] !== 255) return true\n ptr = (ptr + 4)|0\n }\n return false\n}\n\nfunction resetAlpha (dst: Uint8Array, width: number, height: number): void {\n let ptr = 3\n const len = (width * height * 4)|0\n while (ptr < len) { dst[ptr] = 0xFF; ptr = (ptr + 4)|0 }\n}\n\nexport default function resize (options: MathResizeOptions): Uint8Array {\n const src = options.src\n const srcW = options.width\n const srcH = options.height\n const destW = options.toWidth\n const destH = options.toHeight\n const scaleX = options.scaleX || options.toWidth / options.width\n const scaleY = options.scaleY || options.toHeight / options.height\n const offsetX = options.offsetX || 0\n const offsetY = options.offsetY || 0\n const dest = options.dest || new Uint8Array(destW * destH * 4)\n\n const filter = typeof options.filter === 'undefined' ? 'mks2013' : options.filter\n const filtersX = createFilters(filter, srcW, destW, scaleX, offsetX),\n filtersY = createFilters(filter, srcH, destH, scaleY, offsetY)\n\n const tmp = new Uint16Array(destW * srcH * 4)\n\n // Autodetect if alpha channel exists, and use appropriate method\n if (hasAlpha(src, srcW, srcH)) {\n convolveHorWithPre(src, tmp, srcW, srcH, destW, filtersX)\n convolveVertWithPre(tmp, dest, srcH, destW, destH, filtersY)\n } else {\n convolveHor(src, tmp, srcW, srcH, destW, filtersX)\n convolveVert(tmp, dest, srcH, destW, destH, filtersY)\n resetAlpha(dest, destW, destH)\n }\n\n return dest\n}\n","import createFilters from './resize_filter_gen'\nimport type { MathResizeOptions, MathWasmContext, MathImageBuffer } from '../mathlib'\n\nfunction hasAlpha (src: MathImageBuffer, width: number, height: number): boolean {\n let ptr = 3\n const len = (width * height * 4)|0\n while (ptr < len) {\n if (src[ptr] !== 255) return true\n ptr = (ptr + 4)|0\n }\n return false\n}\n\nfunction resetAlpha (dst: Uint8Array, width: number, height: number): void {\n let ptr = 3\n const len = (width * height * 4)|0\n while (ptr < len) { dst[ptr] = 0xFF; ptr = (ptr + 4)|0 }\n}\n\nfunction asUint8Array (src: Int16Array): Uint8Array {\n return new Uint8Array(src.buffer, 0, src.byteLength)\n}\n\nlet IS_LE = true\n// should not crash everything on module load in old browsers\ntry {\n IS_LE = ((new Uint32Array((new Uint8Array([1, 0, 0, 0])).buffer))[0] === 1)\n} catch (__) {}\n\nfunction copyInt16asLE (src: Int16Array, target: Uint8Array, target_offset: number): void {\n if (IS_LE) {\n target.set(asUint8Array(src), target_offset)\n return\n }\n\n for (let ptr = target_offset, i = 0; i < src.length; i++) {\n const data = src[i]\n target[ptr++] = data & 0xFF\n target[ptr++] = (data >> 8) & 0xFF\n }\n}\n\nexport default function resize_wasm (this: MathWasmContext, options: MathResizeOptions): Uint8Array {\n const src = options.src\n const srcW = options.width\n const srcH = options.height\n const destW = options.toWidth\n const destH = options.toHeight\n const scaleX = options.scaleX || options.toWidth / options.width\n const scaleY = options.scaleY || options.toHeight / options.height\n const offsetX = options.offsetX || 0.0\n const offsetY = options.offsetY || 0.0\n const dest = options.dest || new Uint8Array(destW * destH * 4)\n\n const filter = typeof options.filter === 'undefined' ? 'mks2013' : options.filter\n const filtersX = createFilters(filter, srcW, destW, scaleX, offsetX),\n filtersY = createFilters(filter, srcH, destH, scaleY, offsetY)\n\n // destination is 0 too.\n const src_offset = 0\n const src_size = Math.max(src.byteLength, dest.byteLength)\n // buffer between convolve passes\n const tmp_offset = this.__align(src_offset + src_size)\n const tmp_size = srcH * destW * 4 * 2 // 2 bytes per channel\n\n const filtersX_offset = this.__align(tmp_offset + tmp_size)\n const filtersY_offset = this.__align(filtersX_offset + filtersX.byteLength)\n const alloc_bytes = filtersY_offset + filtersY.byteLength\n\n const instance = this.__instance('resize', alloc_bytes)\n\n //\n // Fill memory block with data to process\n //\n\n const mem = new Uint8Array(this.__memory!.buffer)\n const mem32 = new Uint32Array(this.__memory!.buffer)\n\n // 32-bit copy is much faster in chrome\n const src32 = new Uint32Array(src.buffer)\n mem32.set(src32)\n\n // We should guarantee LE bytes order. Filters are not big, so\n // speed difference is not significant vs direct .set()\n copyInt16asLE(filtersX, mem, filtersX_offset)\n copyInt16asLE(filtersY, mem, filtersY_offset)\n\n // Now call webassembly method\n // emsdk does method names with '_'\n const fn = (instance.exports.convolveHV || instance.exports._convolveHV) as Function | undefined\n if (!fn) throw new Error('WASM resize function is not available')\n\n if (hasAlpha(src, srcW, srcH)) {\n fn(filtersX_offset, filtersY_offset, tmp_offset, srcW, srcH, destW, destH, 1)\n } else {\n fn(filtersX_offset, filtersY_offset, tmp_offset, srcW, srcH, destW, destH, 0)\n resetAlpha(dest, destW, destH)\n }\n\n //\n // Copy data back to typed array\n //\n\n // 32-bit copy is much faster in chrome\n const dest32 = new Uint32Array(dest.buffer)\n dest32.set(new Uint32Array(this.__memory!.buffer, 0, destH * destW))\n\n return dest\n}\n","// This is autogenerated file from math.wasm, don't edit.\n//\nexport default 'AGFzbQEAAAAADAZkeWxpbmsAAAAAAAEYA2AGf39/f39/AGAAAGAIf39/f39/f38AAg8BA2VudgZtZW1vcnkCAAADBwYBAAAAAAIGBgF/AEEACweUAQgRX193YXNtX2NhbGxfY3RvcnMAAAtjb252b2x2ZUhvcgABDGNvbnZvbHZlVmVydAACEmNvbnZvbHZlSG9yV2l0aFByZQADE2NvbnZvbHZlVmVydFdpdGhQcmUABApjb252b2x2ZUhWAAUMX19kc29faGFuZGxlAwAYX193YXNtX2FwcGx5X2RhdGFfcmVsb2NzAAAKyA4GAwABC4wDARB/AkAgA0UNACAERQ0AIANBAnQhFQNAQQAhE0EAIQsDQCALQQJqIQcCfyALQQF0IAVqIgYuAQIiC0UEQEEAIQhBACEGQQAhCUEAIQogBwwBCyASIAYuAQBqIQhBACEJQQAhCiALIRRBACEOIAchBkEAIQ8DQCAFIAZBAXRqLgEAIhAgACAIQQJ0aigCACIRQRh2bCAPaiEPIBFB/wFxIBBsIAlqIQkgEUEQdkH/AXEgEGwgDmohDiARQQh2Qf8BcSAQbCAKaiEKIAhBAWohCCAGQQFqIQYgFEEBayIUDQALIAlBB3UhCCAKQQd1IQYgDkEHdSEJIA9BB3UhCiAHIAtqCyELIAEgDEEBdCIHaiAIQQAgCEEAShs7AQAgASAHQQJyaiAGQQAgBkEAShs7AQAgASAHQQRyaiAJQQAgCUEAShs7AQAgASAHQQZyaiAKQQAgCkEAShs7AQAgDCAVaiEMIBNBAWoiEyAERw0ACyANQQFqIg0gAmwhEiANQQJ0IQwgAyANRw0ACwsL2gMBD38CQCADRQ0AIARFDQAgAkECdCEUA0AgCyEMQQAhE0EAIQIDQCACQQJqIQYCfyACQQF0IAVqIgcuAQIiAkUEQEEAIQhBACEHQQAhCkEAIQkgBgwBCyAHLgEAQQJ0IBJqIQhBACEJIAIhCkEAIQ0gBiEHQQAhDkEAIQ8DQCAFIAdBAXRqLgEAIhAgACAIQQF0IhFqLwEAbCAJaiEJIAAgEUEGcmovAQAgEGwgDmohDiAAIBFBBHJqLwEAIBBsIA9qIQ8gACARQQJyai8BACAQbCANaiENIAhBBGohCCAHQQFqIQcgCkEBayIKDQALIAlBB3UhCCANQQd1IQcgDkEHdSEKIA9BB3UhCSACIAZqCyECIAEgDEECdGogB0GAQGtBDnUiBkH/ASAGQf8BSBsiBkEAIAZBAEobQQh0QYD+A3EgCUGAQGtBDnUiBkH/ASAGQf8BSBsiBkEAIAZBAEobQRB0QYCA/AdxIApBgEBrQQ51IgZB/wEgBkH/AUgbIgZBACAGQQBKG0EYdHJyIAhBgEBrQQ51IgZB/wEgBkH/AUgbIgZBACAGQQBKG3I2AgAgAyAMaiEMIBNBAWoiEyAERw0ACyAUIAtBAWoiC2whEiADIAtHDQALCwuSAwEQfwJAIANFDQAgBEUNACADQQJ0IRUDQEEAIRNBACEGA0AgBkECaiEIAn8gBkEBdCAFaiIGLgECIgdFBEBBACEJQQAhDEEAIQ1BACEOIAgMAQsgEiAGLgEAaiEJQQAhDkEAIQ1BACEMIAchFEEAIQ8gCCEGA0AgBSAGQQF0ai4BACAAIAlBAnRqKAIAIhBBGHZsIhEgD2ohDyARIBBBEHZB/wFxbCAMaiEMIBEgEEEIdkH/AXFsIA1qIQ0gESAQQf8BcWwgDmohDiAJQQFqIQkgBkEBaiEGIBRBAWsiFA0ACyAPQQd1IQkgByAIagshBiABIApBAXQiCGogDkH/AW1BB3UiB0EAIAdBAEobOwEAIAEgCEECcmogDUH/AW1BB3UiB0EAIAdBAEobOwEAIAEgCEEEcmogDEH/AW1BB3UiB0EAIAdBAEobOwEAIAEgCEEGcmogCUEAIAlBAEobOwEAIAogFWohCiATQQFqIhMgBEcNAAsgC0EBaiILIAJsIRIgC0ECdCEKIAMgC0cNAAsLC4IEAQ9/AkAgA0UNACAERQ0AIAJBAnQhFANAIAshDEEAIRJBACEHA0AgB0ECaiEKAn8gB0EBdCAFaiICLgECIhNFBEBBACEIQQAhCUEAIQYgCiEHQQAMAQsgAi4BAEECdCARaiEJQQAhByATIQJBACENIAohBkEAIQ5BACEPA0AgBSAGQQF0ai4BACIIIAAgCUEBdCIQai8BAGwgB2ohByAAIBBBBnJqLwEAIAhsIA5qIQ4gACAQQQRyai8BACAIbCAPaiEPIAAgEEECcmovAQAgCGwgDWohDSAJQQRqIQkgBkEBaiEGIAJBAWsiAg0ACyAHQQd1IQggDUEHdSEJIA9BB3UhBiAKIBNqIQcgDkEHdQtBgEBrQQ51IgJB/wEgAkH/AUgbIgJBACACQQBKGyIKQf8BcQRAIAlB/wFsIAJtIQkgCEH/AWwgAm0hCCAGQf8BbCACbSEGCyABIAxBAnRqIAlBgEBrQQ51IgJB/wEgAkH/AUgbIgJBACACQQBKG0EIdEGA/gNxIAZBgEBrQQ51IgJB/wEgAkH/AUgbIgJBACACQQBKG0EQdEGAgPwHcSAKQRh0ciAIQYBAa0EOdSICQf8BIAJB/wFIGyICQQAgAkEAShtycjYCACADIAxqIQwgEkEBaiISIARHDQALIBQgC0EBaiILbCERIAMgC0cNAAsLC0AAIAcEQEEAIAIgAyAEIAUgABADIAJBACAEIAUgBiABEAQPC0EAIAIgAyAEIAUgABABIAJBACAEIAUgBiABEAIL'\n","import fn from './resize'\nimport wasm_fn from './resize_wasm'\nimport wasm_src from './convolve_wasm_base64'\n\nexport default {\n name: 'resize' as const,\n fn,\n wasm_fn,\n wasm_src\n}\n","// Collection of math functions\n//\n// 1. Combine components together\n// 2. Has async init to load wasm modules\n//\nimport { MultiMath } from 'multimath'\n\nimport mm_unsharp_mask from './mm_unsharp_mask'\nimport mm_resize from './mm_resize'\n\nexport type MathFeaturesMap = { js: boolean; wasm: boolean }\nexport type { MultiMathPlugin as MathPlugin } from 'multimath'\nexport type MathImageBuffer = Uint8Array | Uint8ClampedArray\nexport type MathWasmContext = MultiMath\n\nexport type MathResizeFilter = 'box' | 'hamming' | 'lanczos2' | 'lanczos3' | 'mks2013'\n\nexport interface MathResizeOptions {\n src: MathImageBuffer\n width: number\n height: number\n toWidth: number\n toHeight: number\n dest?: Uint8Array\n scaleX: number\n scaleY: number\n offsetX: number\n offsetY: number\n filter: MathResizeFilter\n}\n\nexport interface MathResizeAndUnsharpOptions extends MathResizeOptions {\n unsharpAmount: number\n unsharpRadius: number\n unsharpThreshold: number\n}\n\nexport default class MathLib extends MultiMath {\n declare features: MathFeaturesMap\n declare resize: typeof mm_resize.fn\n declare unsharp_mask: typeof mm_unsharp_mask.fn\n\n constructor (requested_features?: readonly string[]) {\n const __requested_features = requested_features || []\n\n const features = {\n js: __requested_features.indexOf('js') >= 0,\n wasm: __requested_features.indexOf('wasm') >= 0\n }\n\n super(features)\n\n this.features = {\n js: features.js,\n wasm: features.wasm && this.has_wasm()\n }\n\n this.use(mm_unsharp_mask)\n this.use(mm_resize)\n }\n\n resizeAndUnsharp (options: MathResizeAndUnsharpOptions): Uint8Array {\n const result = this.resize(options)\n\n if (options.unsharpAmount) {\n this.unsharp_mask(\n result,\n options.toWidth,\n options.toHeight,\n options.unsharpAmount,\n options.unsharpRadius,\n options.unsharpThreshold\n )\n }\n\n return result\n }\n}\n","const GC_INTERVAL = 100\n\n// Acquired items\nexport interface PoolResource<T> {\n value: T\n release: () => void\n}\n\n// Internal storage format\ninterface PoolResourceDescriptor<T> {\n id: number\n lastUsed: number\n value: T\n destroy: () => void\n}\n\nexport default class Pool<T> {\n create: () => { value: T, destroy: () => void }\n available: Array<PoolResourceDescriptor<T>>\n acquired: Record<number, PoolResourceDescriptor<T>>\n lastId: number\n timeoutId: ReturnType<typeof setTimeout> | 0\n idle: number\n\n constructor (create: () => { value: T, destroy: () => void }, idle?: number) {\n this.create = create\n\n this.available = []\n this.acquired = {}\n this.lastId = 1\n\n this.timeoutId = 0\n this.idle = idle || 2000\n }\n\n acquire (): PoolResource<T> {\n let descriptor: PoolResourceDescriptor<T>\n\n if (this.available.length !== 0) {\n descriptor = this.available.pop() as PoolResourceDescriptor<T>\n } else {\n const init = this.create()\n descriptor = { ...init, id: this.lastId++, lastUsed: 0 }\n }\n this.acquired[descriptor.id] = descriptor\n return { value: descriptor.value, release: () => this.release(descriptor) }\n }\n\n release (descriptor: PoolResourceDescriptor<T>): void {\n delete this.acquired[descriptor.id]\n\n descriptor.lastUsed = Date.now()\n this.available.push(descriptor)\n\n if (this.timeoutId === 0) {\n this.timeoutId = setTimeout(() => this.gc(), GC_INTERVAL)\n }\n }\n\n gc (): void {\n const now = Date.now()\n\n this.available = this.available.filter(descriptor => {\n if (now - descriptor.lastUsed > this.idle) {\n descriptor.destroy()\n return false\n }\n return true\n })\n\n if (this.available.length !== 0) {\n this.timeoutId = setTimeout(() => this.gc(), GC_INTERVAL)\n } else {\n this.timeoutId = 0\n }\n }\n}\n","import type { CibResizeQuality, Filter, Limiter, PicaCanvas } from './types'\n\n// Uses constructor.name — safe because all targets are browser built-ins or\n// native Node.js library classes that minifiers don't rename. If this ever\n// breaks after a toolchain change, fall back to Object.prototype.toString:\n// Object.prototype.toString.call(obj) === '[object HTMLCanvasElement]' etc.\nfunction objClass (obj: unknown): string {\n return (obj as any)?.constructor?.name ?? ''\n}\n\nexport function isCanvas (element: unknown): element is PicaCanvas {\n const cname = objClass(element)\n\n return cname === 'HTMLCanvasElement' ||\n cname === 'OffscreenCanvas' ||\n cname === 'Canvas' /* node-canvas */ ||\n cname === 'CanvasElement' /* @napi-rs/canvas */\n}\n\nexport function isImage (element: unknown): element is HTMLImageElement {\n return objClass(element) === 'HTMLImageElement'\n}\n\nexport function isImageBitmap (element: unknown): element is ImageBitmap {\n return objClass(element) === 'ImageBitmap'\n}\n\nexport function limiter (concurrency: number): Limiter {\n let active = 0\n const queue: Array<() => void> = []\n\n function roll () {\n if (active < concurrency && queue.length) {\n active++\n queue.shift()?.()\n }\n }\n\n return function limit<T> (fn: () => Promise<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n queue.push(() => {\n fn().then(\n result => {\n resolve(result)\n active--\n roll()\n },\n err => {\n reject(err)\n active--\n roll()\n }\n )\n })\n\n roll()\n })\n }\n}\n\nexport function cib_quality_name (num: number): ResizeQuality {\n switch (num) {\n case 0: return 'pixelated'\n case 1: return 'low'\n case 2: return 'medium'\n }\n return 'high'\n}\n\nconst CIB_QUALITY_FILTERS: Filter[] = ['box', 'hamming', 'lanczos2', 'lanczos3']\n\nexport function cib_quality_filter (num: CibResizeQuality): Filter {\n return CIB_QUALITY_FILTERS[num]\n}\n\nexport function is_cib_filter (filter: Filter): boolean {\n return CIB_QUALITY_FILTERS.indexOf(filter) >= 0\n}\n\nexport function filter_to_cib_quality (filter: Filter): CibResizeQuality | undefined {\n const index = CIB_QUALITY_FILTERS.indexOf(filter)\n\n return index >= 0 ? index as CibResizeQuality : undefined\n}\n\ntype ResizeQuality = 'pixelated' | 'low' | 'medium' | 'high'\n","export type Stage = [width: number, height: number]\n\n// Add intermediate resizing steps when scaling down by a very large factor.\n//\n// For example, when resizing 10000x10000 down to 10x10, it'll resize it to\n// 300x300 first.\n//\n// It's needed because tiler has issues when the entire tile is scaled down\n// to a few pixels (1024px source tile with border size 3 should result in\n// at least 3+3+2 = 8px target tile, so max scale factor is 128 here).\n//\n// Also, adding intermediate steps can speed up processing if we use lower\n// quality algorithms for first stages.\n//\n\n// min size = 0 results in infinite loop,\n// min size = 1 can consume large amount of memory\nconst MIN_INNER_TILE_SIZE = 2\n\nconst DEST_TILE_BORDER = 3 // Max possible filter window size\n\nexport default function createStages (\n fromWidth: number,\n fromHeight: number,\n toWidth: number,\n toHeight: number,\n srcTileSize: number\n): Stage[] {\n const scaleX = toWidth / fromWidth\n const scaleY = toHeight / fromHeight\n\n // derived from createRegions equation:\n // innerTileWidth = pixelFloor(srcTileSize * scaleX) - 2 * destTileBorder;\n const minScale = (2 * DEST_TILE_BORDER + MIN_INNER_TILE_SIZE + 1) / srcTileSize\n\n // refuse to scale image multiple times by less than twice each time,\n // it could only happen because of invalid options\n if (minScale > 0.5) return [[toWidth, toHeight]]\n\n const stageCount = Math.ceil(Math.log(Math.min(scaleX, scaleY)) / Math.log(minScale))\n\n // no additional resizes are necessary,\n // stageCount can be zero or be negative when enlarging the image\n if (stageCount <= 1) return [[toWidth, toHeight]]\n\n const result: Stage[] = []\n\n for (let i = 0; i < stageCount; i++) {\n const width = Math.round(\n Math.pow(\n Math.pow(fromWidth, stageCount - i - 1) * Math.pow(toWidth, i + 1),\n 1 / stageCount\n )\n )\n\n const height = Math.round(\n Math.pow(\n Math.pow(fromHeight, stageCount - i - 1) * Math.pow(toHeight, i + 1),\n 1 / stageCount\n )\n )\n\n result.push([width, height])\n }\n\n return result\n}\n","export interface Tile {\n toX: number\n toY: number\n toWidth: number\n toHeight: number\n toInnerX: number\n toInnerY: number\n toInnerWidth: number\n toInnerHeight: number\n offsetX: number\n of