{"version":3,"file":"ndarray-lanczos.cjs","sources":["../src/index.ts","../vendor/filters.ts","../src/convolve.ts"],"sourcesContent":["import ndarray, { NdArray } from 'ndarray';\nimport { filters, TypedArrayConstructor } from '../vendor/filters.js';\nimport { convolve } from './convolve.js';\n\nenum Method {\n\tLANCZOS_3 = 3,\n\tLANCZOS_2 = 2,\n}\n\nexport type SupportedTypes = Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array\n\nfunction resize(\n\tsrc: NdArray<SupportedTypes | number[]>,\n\tdst: NdArray<SupportedTypes>, method: Method\n): void {\n\tif (src.shape.length !== 3 || dst.shape.length !== 3)\n\t\tthrow new TypeError\n\t\t\t('Input and output must have exactly 3 dimensions (width, height and colorspace)');\n\n\tconst [srcWidth, srcHeight] = src.shape;\n\tconst [dstWidth, dstHeight] = dst.shape;\n\n\tconst ratioX = dstWidth / srcWidth;\n\tconst ratioY = dstHeight / srcHeight;\n\n\tlet floatType, intType;\n\tswitch (dst.dtype) {\n\t\tcase 'uint8_clamped':\n\t\tcase 'uint8':\n\t\t\tfloatType = Float32Array;\n\t\t\tintType = Int16Array;\n\t\t\tbreak;\n\t\tcase 'uint16':\n\t\tcase 'uint32':\n\t\t\tfloatType = Float64Array;\n\t\t\tintType = Int32Array;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tthrow TypeError(`Unsupported data type ${dst.dtype}`);\n\t}\n\tconst fixedFracBits = intType.BYTES_PER_ELEMENT * 7;\n\n\tconst filtersX = filters(srcWidth, dstWidth, ratioX, 0, method === Method.LANCZOS_2,\n\t\tfloatType, intType, fixedFracBits);\n\tconst filtersY = filters(srcHeight, dstHeight, ratioY, 0, method === Method.LANCZOS_2,\n\t\tfloatType, intType, fixedFracBits);\n\n\tconst constructor = dst.data.constructor as TypedArrayConstructor;\n\tconst tmp = ndarray(new constructor(dstWidth * srcHeight * 4), [srcHeight, dstWidth, 4]);\n\tconst tmpTranspose = tmp.transpose(1, 0);\n\tconst dstTranspose = dst.transpose(1, 0);\n\n\tconvolve(src, tmpTranspose, filtersX, fixedFracBits);\n\tconvolve(tmp, dstTranspose, filtersY, fixedFracBits);\n}\n\nexport function lanczos3(src: NdArray<SupportedTypes | number[]>, dst: NdArray<SupportedTypes>): void {\n\tresize(src, dst, Method.LANCZOS_3);\n}\n\nexport function lanczos2(src: NdArray<SupportedTypes | number[]>, dst: NdArray<SupportedTypes>): void {\n\tresize(src, dst, Method.LANCZOS_2);\n}\n","export type TypedArrayConstructor = Int8ArrayConstructor | Int16ArrayConstructor | Int32ArrayConstructor |\n\tUint8ArrayConstructor | Uint8ClampedArrayConstructor | Uint16ArrayConstructor |\n\tUint32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor\n\nconst filterValue = ( x: number, a: 2 | 3 ) => {\n\tif ( x <= -a || x >= a ) return 0\n\n\t// appears to do nothing?\n\tif ( x > -1.19209290e-07 && x < 1.19209290e-07 ) return 1\n\n\tconst xPi = x * Math.PI\n\n\treturn ( Math.sin( xPi ) / xPi ) * Math.sin( xPi / a ) / ( xPi / a )\n}\n\nexport const filters = (\n\tsrcSize: number,\n\tdestSize: number,\n\tscale: number,\n\toffset: number,\n\tuse2: boolean,\n\tfloatType: TypedArrayConstructor,\n\tintType: TypedArrayConstructor,\n\tfixedFracBits: number\n) => {\n\tconst mul = (2 ** fixedFracBits) - 1\n\tconst toFixedPoint = (value: number) => Math.round(value * mul)\n\n\tconst a = use2 ? 2 : 3\n\tconst scaleInverted = 1 / scale\n\tconst scaleClamped = Math.min( 1, scale ) // For upscale\n\n\t// Filter window (averaging interval), scaled to src image\n\tconst srcWindow = a / scaleClamped\n\n\tconst maxFilterElementSize = Math.floor( ( srcWindow + 1 ) * 2 )\n\tconst packedFilter = new intType( ( maxFilterElementSize + 2 ) * destSize )\n\tlet packedFilterPtr = 0\n\n\t// For each destination pixel calculate source range and built filter values\n\tfor ( let destPixel = 0; destPixel < destSize; destPixel++ ) {\n\n\t\t// Scaling should be done relative to central pixel point\n\t\tconst sourcePixel = ( destPixel + 0.5 ) * scaleInverted + offset\n\t\tconst sourceFirst = Math.max( 0, Math.floor( sourcePixel - srcWindow ) )\n\t\tconst sourceLast = Math.min( srcSize - 1, Math.ceil( sourcePixel + srcWindow ) )\n\n\t\tconst filterElementSize = sourceLast - sourceFirst + 1\n\t\tconst floatFilter = new floatType( filterElementSize )\n\t\tconst fxpFilter = new intType( filterElementSize )\n\n\t\tlet total = 0\n\n\t\t// Fill filter values for calculated range\n\t\tlet index = 0\n\t\tfor ( let pixel = sourceFirst; pixel <= sourceLast; pixel++ ) {\n\t\t\tconst floatValue = filterValue( ( ( pixel + 0.5 ) - sourcePixel ) * scaleClamped, a )\n\n\t\t\ttotal += floatValue\n\t\t\tfloatFilter[ index ] = floatValue\n\n\t\t\tindex++\n\t\t}\n\n\t\t// Normalize filter, convert to fixed point and accumulate conversion error\n\t\tlet filterTotal = 0\n\n\t\tfor ( let index = 0; index < floatFilter.length; index++ ) {\n\t\t\tconst filterValue = floatFilter[ index ] / total\n\n\t\t\tfilterTotal += filterValue\n\t\t\tfxpFilter[ index ] = toFixedPoint( filterValue )\n\t\t}\n\n\t\t// Compensate normalization error, to minimize brightness drift\n\t\tfxpFilter[ destSize >> 1 ] += toFixedPoint( 1 - filterTotal )\n\n\t\t//\n\t\t// Now pack filter to useable form\n\t\t//\n\t\t// 1. Trim heading and tailing zero values, and compensate shitf/length\n\t\t// 2. Put all to single array in this format:\n\t\t//\n\t\t//    [ pos shift, data length, value1, value2, value3, ... ]\n\t\t//\n\t\tlet leftNotEmpty = 0\n\t\twhile ( leftNotEmpty < fxpFilter.length && fxpFilter[ leftNotEmpty ] === 0 ) {\n\t\t\tleftNotEmpty++\n\t\t}\n\n\t\tlet rightNotEmpty = fxpFilter.length - 1\n\t\twhile ( rightNotEmpty > 0 && fxpFilter[ rightNotEmpty ] === 0 ) {\n\t\t\trightNotEmpty--\n\t\t}\n\n\t\tconst filterShift = sourceFirst + leftNotEmpty\n\t\tconst filterSize = rightNotEmpty - leftNotEmpty + 1\n\n\t\tpackedFilter[ packedFilterPtr++ ] = filterShift // shift\n\t\tpackedFilter[ packedFilterPtr++ ] = filterSize // size\n\n\t\tpackedFilter.set( fxpFilter.subarray( leftNotEmpty, rightNotEmpty + 1 ), packedFilterPtr )\n\t\tpackedFilterPtr += filterSize\n\t}\n\n\treturn packedFilter\n}\n","import type { NdArray, TypedArray } from 'ndarray';\n\nexport const convolve = (src: NdArray<TypedArray | number[]>, dst: NdArray<TypedArray>, filters: TypedArray, fixedFracBits: number) => {\n\tconst [_, srcHeight] = src.shape;\n\tconst [dstWidth] = dst.shape;\n\n\tconst maxValue = 2 ** (dst.data.BYTES_PER_ELEMENT * 8) - 1;\n\tconst clamp = (v: number): number => v < 0 ? 0 : (v > maxValue ? maxValue : v);\n\tconst fixedFracMul = 2 ** (fixedFracBits - 1);\n\tconst fixedFracMul2 = 2 * fixedFracMul;\n\n\t// For each row\n\tfor (let srcY = 0; srcY < srcHeight; srcY++) {\n\t\tconst dstY = srcY;\n\n\t\t// Apply precomputed filters to each destination row point\n\t\tlet filterPtr = 0;\n\t\tfor (let dstX = 0; dstX < dstWidth; dstX++) {\n\t\t\t// Get the filter that determines the current output pixel.\n\t\t\tlet srcX = filters[filterPtr++];\n\n\t\t\tlet r = 0;\n\t\t\tlet g = 0;\n\t\t\tlet b = 0;\n\t\t\tlet a = 0;\n\n\t\t\t// Apply the filter to the row to get the destination pixel r, g, b, a\n\t\t\tfor (let filterSize = filters[filterPtr++]; filterSize > 0; filterSize--) {\n\t\t\t\tconst filterValue = filters[filterPtr++];\n\n\t\t\t\tr = ( r + filterValue * src.get(srcX, srcY, 0) );\n\t\t\t\tg = ( g + filterValue * src.get(srcX, srcY, 1) );\n\t\t\t\tb = ( b + filterValue * src.get(srcX, srcY, 2) );\n\t\t\t\ta = ( a + filterValue * src.get(srcX, srcY, 3) );\n\n\t\t\t\tsrcX++;\n\t\t\t}\n\n\t\t\t// Bring this value back in range. All of the filter scaling factors\n\t\t\t// are in fixed point with fixedFracBits bits of fractional part.\n\t\t\t//\n\t\t\t// (!) Add 1/2 of value before clamping to get proper rounding. In other\n\t\t\t// case brightness loss will be noticeable if you resize image with white\n\t\t\t// border and place it on white background.\n\t\t\tdst.set(dstX, dstY, 0, clamp( ( r + fixedFracMul ) / fixedFracMul2 ) );\n\t\t\tdst.set(dstX, dstY, 1, clamp( ( g + fixedFracMul ) / fixedFracMul2 ) );\n\t\t\tdst.set(dstX, dstY, 2, clamp( ( b + fixedFracMul ) / fixedFracMul2 ) );\n\t\t\tdst.set(dstX, dstY, 3, clamp( ( a + fixedFracMul ) / fixedFracMul2 ) );\n\t\t}\n\t}\n}\n"],"names":["Method","filterValue","x","a","xPi","Math","PI","sin","filters","srcSize","destSize","scale","offset","use2","floatType","intType","fixedFracBits","mul","toFixedPoint","value","round","scaleInverted","scaleClamped","min","srcWindow","packedFilter","floor","packedFilterPtr","destPixel","sourcePixel","sourceFirst","max","sourceLast","ceil","filterElementSize","floatFilter","fxpFilter","total","index","pixel","floatValue","filterTotal","length","leftNotEmpty","rightNotEmpty","filterSize","set","subarray","convolve","src","dst","srcHeight","shape","dstWidth","maxValue","data","BYTES_PER_ELEMENT","clamp","v","fixedFracMul","fixedFracMul2","srcY","dstY","filterPtr","dstX","srcX","r","g","b","get","resize","method","TypeError","srcWidth","dstHeight","ratioX","ratioY","dtype","Float32Array","Int16Array","Float64Array","Int32Array","filtersX","LANCZOS_2","filtersY","tmp","ndarray","constructor","tmpTranspose","transpose","dstTranspose","LANCZOS_3"],"mappings":"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"}