Spaces:
Running
Running
| import type { Buffer2D, DwtResult } from './types.js'; | |
| /** | |
| * Create a 2D buffer | |
| */ | |
| export function createBuffer2D(width: number, height: number): Buffer2D { | |
| return { data: new Float64Array(width * height), width, height }; | |
| } | |
| /** | |
| * Copy a Buffer2D | |
| */ | |
| export function copyBuffer2D(buf: Buffer2D): Buffer2D { | |
| return { data: new Float64Array(buf.data), width: buf.width, height: buf.height }; | |
| } | |
| /** | |
| * Get value from 2D buffer | |
| */ | |
| function get(buf: Buffer2D, x: number, y: number): number { | |
| return buf.data[y * buf.width + x]; | |
| } | |
| /** | |
| * Set value in 2D buffer | |
| */ | |
| function set(buf: Buffer2D, x: number, y: number, val: number): void { | |
| buf.data[y * buf.width + x] = val; | |
| } | |
| // Reusable temp buffer for Haar transforms β grown as needed | |
| let _haarTmp: Float64Array | null = null; | |
| function getHaarTmp(len: number): Float64Array { | |
| if (!_haarTmp || _haarTmp.length < len) { | |
| _haarTmp = new Float64Array(len); | |
| } | |
| return _haarTmp; | |
| } | |
| /** | |
| * 1D Haar forward transform (in-place on a row/column slice) | |
| */ | |
| function haarForward1D(input: Float64Array, length: number): void { | |
| const half = length >> 1; | |
| const tmp = getHaarTmp(length); | |
| const s = Math.SQRT1_2; // 1/sqrt(2) | |
| for (let i = 0; i < half; i++) { | |
| const a = input[2 * i]; | |
| const b = input[2 * i + 1]; | |
| tmp[i] = (a + b) * s; // approximation (low) | |
| tmp[half + i] = (a - b) * s; // detail (high) | |
| } | |
| for (let i = 0; i < length; i++) input[i] = tmp[i]; | |
| } | |
| /** | |
| * 1D Haar inverse transform (in-place on a row/column slice) | |
| */ | |
| function haarInverse1D(input: Float64Array, length: number): void { | |
| const half = length >> 1; | |
| const tmp = getHaarTmp(length); | |
| const s = Math.SQRT1_2; | |
| for (let i = 0; i < half; i++) { | |
| const low = input[i]; | |
| const high = input[half + i]; | |
| tmp[2 * i] = (low + high) * s; | |
| tmp[2 * i + 1] = (low - high) * s; | |
| } | |
| for (let i = 0; i < length; i++) input[i] = tmp[i]; | |
| } | |
| /** | |
| * Forward 2D Haar DWT (one level) | |
| * Transforms the top-left (w x h) region of the buffer | |
| */ | |
| function dwtForward2DLevel(buf: Buffer2D, w: number, h: number): void { | |
| // Transform rows | |
| const rowBuf = new Float64Array(w); | |
| for (let y = 0; y < h; y++) { | |
| const off = y * buf.width; | |
| for (let x = 0; x < w; x++) rowBuf[x] = buf.data[off + x]; | |
| haarForward1D(rowBuf, w); | |
| for (let x = 0; x < w; x++) buf.data[off + x] = rowBuf[x]; | |
| } | |
| // Transform columns | |
| const colBuf = new Float64Array(h); | |
| for (let x = 0; x < w; x++) { | |
| for (let y = 0; y < h; y++) colBuf[y] = buf.data[y * buf.width + x]; | |
| haarForward1D(colBuf, h); | |
| for (let y = 0; y < h; y++) buf.data[y * buf.width + x] = colBuf[y]; | |
| } | |
| } | |
| /** | |
| * Inverse 2D Haar DWT (one level) | |
| */ | |
| function dwtInverse2DLevel(buf: Buffer2D, w: number, h: number): void { | |
| // Inverse columns | |
| const colBuf = new Float64Array(h); | |
| for (let x = 0; x < w; x++) { | |
| for (let y = 0; y < h; y++) colBuf[y] = buf.data[y * buf.width + x]; | |
| haarInverse1D(colBuf, h); | |
| for (let y = 0; y < h; y++) buf.data[y * buf.width + x] = colBuf[y]; | |
| } | |
| // Inverse rows | |
| const rowBuf = new Float64Array(w); | |
| for (let y = 0; y < h; y++) { | |
| const off = y * buf.width; | |
| for (let x = 0; x < w; x++) rowBuf[x] = buf.data[off + x]; | |
| haarInverse1D(rowBuf, w); | |
| for (let x = 0; x < w; x++) buf.data[off + x] = rowBuf[x]; | |
| } | |
| } | |
| /** | |
| * Extract a subband from the DWT buffer | |
| * After one level of DWT on a (w x h) region: | |
| * LL = top-left (w/2 x h/2) | |
| * LH = top-right (w/2 x h/2) β vertical detail | |
| * HL = bottom-left (w/2 x h/2) β horizontal detail | |
| * HH = bottom-right (w/2 x h/2) β diagonal detail | |
| */ | |
| export function extractSubband( | |
| buf: Buffer2D, | |
| w: number, | |
| h: number, | |
| band: 'LL' | 'LH' | 'HL' | 'HH' | |
| ): Buffer2D { | |
| const hw = w >> 1; | |
| const hh = h >> 1; | |
| const offX = band === 'LH' || band === 'HH' ? hw : 0; | |
| const offY = band === 'HL' || band === 'HH' ? hh : 0; | |
| const out = createBuffer2D(hw, hh); | |
| for (let y = 0; y < hh; y++) { | |
| const srcOff = (offY + y) * buf.width + offX; | |
| const dstOff = y * hw; | |
| for (let x = 0; x < hw; x++) { | |
| out.data[dstOff + x] = buf.data[srcOff + x]; | |
| } | |
| } | |
| return out; | |
| } | |
| /** | |
| * Write a subband back into the DWT buffer | |
| */ | |
| export function writeSubband( | |
| buf: Buffer2D, | |
| w: number, | |
| h: number, | |
| band: 'LL' | 'LH' | 'HL' | 'HH', | |
| subband: Buffer2D | |
| ): void { | |
| const hw = w >> 1; | |
| const hh = h >> 1; | |
| const offX = band === 'LH' || band === 'HH' ? hw : 0; | |
| const offY = band === 'HL' || band === 'HH' ? hh : 0; | |
| for (let y = 0; y < hh; y++) { | |
| const dstOff = (offY + y) * buf.width + offX; | |
| const srcOff = y * hw; | |
| for (let x = 0; x < hw; x++) { | |
| buf.data[dstOff + x] = subband.data[srcOff + x]; | |
| } | |
| } | |
| } | |
| /** | |
| * Multi-level forward DWT | |
| * Returns the modified buffer and subband info for reconstruction | |
| */ | |
| export function dwtForward(input: Buffer2D, levels: number): { buf: Buffer2D; dims: Array<{ w: number; h: number }> } { | |
| const buf = copyBuffer2D(input); | |
| const dims: Array<{ w: number; h: number }> = []; | |
| let w = buf.width; | |
| let h = buf.height; | |
| for (let l = 0; l < levels; l++) { | |
| // Ensure even dimensions | |
| w = w & ~1; | |
| h = h & ~1; | |
| dims.push({ w, h }); | |
| dwtForward2DLevel(buf, w, h); | |
| w >>= 1; | |
| h >>= 1; | |
| } | |
| return { buf, dims }; | |
| } | |
| /** | |
| * Multi-level inverse DWT | |
| */ | |
| export function dwtInverse(buf: Buffer2D, dims: Array<{ w: number; h: number }>): void { | |
| for (let l = dims.length - 1; l >= 0; l--) { | |
| const { w, h } = dims[l]; | |
| dwtInverse2DLevel(buf, w, h); | |
| } | |
| } | |
| /** | |
| * Convert a Uint8Array Y plane to a Float64 Buffer2D | |
| */ | |
| export function yPlaneToBuffer(yPlane: Uint8Array, width: number, height: number): Buffer2D { | |
| const buf = createBuffer2D(width, height); | |
| for (let i = 0; i < width * height; i++) { | |
| buf.data[i] = yPlane[i]; | |
| } | |
| return buf; | |
| } | |
| /** | |
| * Convert a Float64 Buffer2D back to a Uint8Array Y plane (clamped to 0β255) | |
| */ | |
| export function bufferToYPlane(buf: Buffer2D): Uint8Array { | |
| const out = new Uint8Array(buf.width * buf.height); | |
| for (let i = 0; i < out.length; i++) { | |
| out[i] = Math.max(0, Math.min(255, Math.round(buf.data[i]))); | |
| } | |
| return out; | |
| } | |