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;
}