Spaces:
Running
Running
File size: 6,122 Bytes
f2f99a3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | 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;
}
|