Spaces:
Running
Running
| /** | |
| * 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]; | |
| } | |
| } | |
| } | |