File size: 4,379 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
/**
 * 8x8 DCT forward and inverse transforms
 * Separable implementation: rows then columns β€” O(N^3) instead of O(N^4)
 * Reuses temp buffers to avoid per-call allocations
 */

const N = 8;

// Precompute cosine table: cos((2i+1)*j*PI / 16) for i,j in [0,8)
const COS_TABLE = new Float64Array(N * N);
for (let i = 0; i < N; i++) {
  for (let j = 0; j < N; j++) {
    COS_TABLE[i * N + j] = Math.cos(((2 * i + 1) * j * Math.PI) / (2 * N));
  }
}

// Precompute alpha normalization table: alpha(u) * alpha(v) for all (u,v)
const ALPHA_0 = 1 / Math.sqrt(N);
const ALPHA_K = Math.sqrt(2 / N);
const ALPHA_UV = new Float64Array(N * N);
const ALPHA = new Float64Array(N);
for (let k = 0; k < N; k++) ALPHA[k] = k === 0 ? ALPHA_0 : ALPHA_K;
for (let u = 0; u < N; u++) {
  for (let v = 0; v < N; v++) {
    ALPHA_UV[u * N + v] = ALPHA[u] * ALPHA[v];
  }
}

// Precompute scaled cosine: ALPHA[k] * COS_TABLE[i*N+k] for forward transform
const SCALED_COS = new Float64Array(N * N);
for (let i = 0; i < N; i++) {
  for (let k = 0; k < N; k++) {
    SCALED_COS[i * N + k] = ALPHA[k] * COS_TABLE[i * N + k];
  }
}

// Shared temp buffers (avoids allocation per call)
const _temp64 = new Float64Array(64);
const _temp8 = new Float64Array(8);

/**
 * Forward 8x8 DCT on a block (in-place)
 * Separable: transform rows, then columns β€” O(8^3) = 512 ops vs O(8^4) = 4096
 */
export function dctForward8x8(block: Float64Array): void {
  // Step 1: Transform each row (spatial x β†’ frequency v)
  for (let x = 0; x < N; x++) {
    const rowOff = x * N;
    for (let v = 0; v < N; v++) {
      let sum = 0;
      for (let y = 0; y < N; y++) {
        sum += block[rowOff + y] * COS_TABLE[y * N + v];
      }
      _temp64[rowOff + v] = ALPHA[v] * sum;
    }
  }

  // Step 2: Transform each column (spatial x β†’ frequency u)
  for (let v = 0; v < N; v++) {
    for (let u = 0; u < N; u++) {
      let sum = 0;
      for (let x = 0; x < N; x++) {
        sum += _temp64[x * N + v] * COS_TABLE[x * N + u];
      }
      block[u * N + v] = ALPHA[u] * sum;
    }
  }
}

/**
 * Inverse 8x8 DCT on a block (in-place)
 * Separable: inverse columns, then inverse rows
 */
export function dctInverse8x8(block: Float64Array): void {
  // Step 1: Inverse transform columns (frequency u β†’ spatial x)
  for (let v = 0; v < N; v++) {
    for (let x = 0; x < N; x++) {
      let sum = 0;
      for (let u = 0; u < N; u++) {
        sum += ALPHA[u] * block[u * N + v] * COS_TABLE[x * N + u];
      }
      _temp64[x * N + v] = sum;
    }
  }

  // Step 2: Inverse transform rows (frequency v β†’ spatial y)
  for (let x = 0; x < N; x++) {
    const rowOff = x * N;
    for (let y = 0; y < N; y++) {
      let sum = 0;
      for (let v = 0; v < N; v++) {
        sum += ALPHA[v] * _temp64[rowOff + v] * COS_TABLE[y * N + v];
      }
      block[rowOff + y] = sum;
    }
  }
}

/**
 * Zigzag scan order for 8x8 block
 * Maps zigzag index β†’ [row, col]
 */
export const ZIGZAG_ORDER: [number, number][] = [
  [0,0],[0,1],[1,0],[2,0],[1,1],[0,2],[0,3],[1,2],
  [2,1],[3,0],[4,0],[3,1],[2,2],[1,3],[0,4],[0,5],
  [1,4],[2,3],[3,2],[4,1],[5,0],[6,0],[5,1],[4,2],
  [3,3],[2,4],[1,5],[0,6],[0,7],[1,6],[2,5],[3,4],
  [4,3],[5,2],[6,1],[7,0],[7,1],[6,2],[5,3],[4,4],
  [3,5],[2,6],[1,7],[2,7],[3,6],[4,5],[5,4],[6,3],
  [7,2],[7,3],[6,4],[5,5],[4,6],[3,7],[4,7],[5,6],
  [6,5],[7,4],[7,5],[6,6],[5,7],[6,7],[7,6],[7,7],
];

/**
 * Extract an 8x8 block from a subband into a reusable buffer
 */
export function extractBlock(
  data: Float64Array,
  width: number,
  blockRow: number,
  blockCol: number,
  out?: Float64Array
): Float64Array {
  const block = out || new Float64Array(64);
  const startY = blockRow * 8;
  const startX = blockCol * 8;
  for (let r = 0; r < 8; r++) {
    const srcOff = (startY + r) * width + startX;
    const dstOff = r * 8;
    for (let c = 0; c < 8; c++) {
      block[dstOff + c] = data[srcOff + c];
    }
  }
  return block;
}

/**
 * Write an 8x8 block back into a subband
 */
export function writeBlock(
  data: Float64Array,
  width: number,
  blockRow: number,
  blockCol: number,
  block: Float64Array
): void {
  const startY = blockRow * 8;
  const startX = blockCol * 8;
  for (let r = 0; r < 8; r++) {
    const dstOff = (startY + r) * width + startX;
    const srcOff = r * 8;
    for (let c = 0; c < 8; c++) {
      data[dstOff + c] = block[srcOff + c];
    }
  }
}