| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package jpeg |
| |
| // Discrete Cosine Transformation (DCT) implementations using the algorithm from |
| // Christoph Loeffler, Adriaan Lightenberg, and George S. Mostchytz, |
| // “Practical Fast 1-D DCT Algorithms with 11 Multiplications,” ICASSP 1989. |
| // https://ieeexplore.ieee.org/document/266596 |
| // |
| // Since the paper is paywalled, the rest of this comment gives a summary. |
| // |
| // A 1-dimensional forward DCT (1D FDCT) takes as input 8 values x0..x7 |
| // and transforms them in place into the result values. |
| // |
| // The mathematical definition of the N-point 1D FDCT is: |
| // |
| // X[k] = α_k Σ_n x[n] * cos (2n+1)*k*π/2N |
| // |
| // where α₀ = √2 and α_k = 1 for k > 0. |
| // |
| // For our purposes, N=8, so the angles end up being multiples of π/16. |
| // The most direct implementation of this definition would require 64 multiplications. |
| // |
| // Loeffler's paper presents a more efficient computation that requires only |
| // 11 multiplications and works in terms of three basic operations: |
| // |
| // - A “butterfly” x0, x1 = x0+x1, x0-x1. |
| // The inverse is x0, x1 = (x0+x1)/2, (x0-x1)/2. |
| // |
| // - A scaling of x0 by k: x0 *= k. The inverse is scaling by 1/k. |
| // |
| // - A rotation of x0, x1 by θ, defined as: |
| // x0, x1 = x0 cos θ + x1 sin θ, -x0 sin θ + x1 cos θ. |
| // The inverse is rotation by -θ. |
| // |
| // The algorithm proceeds in four stages: |
| // |
| // Stage 1: |
| // - butterfly x0, x7; x1, x6; x2, x5; x3, x4. |
| // |
| // Stage 2: |
| // - butterfly x0, x3; x1, x2 |
| // - rotate x4, x7 by 3π/16 |
| // - rotate x5, x6 by π/16. |
| // |
| // Stage 3: |
| // - butterfly x0, x1; x4, x6; x7, x5 |
| // - rotate x2, x3 by 6π/16 and scale by √2. |
| // |
| // Stage 4: |
| // - butterfly x7, x4 |
| // - scale x5, x6 by √2. |
| // |
| // Finally, the values are permuted. The permutation can be read as either: |
| // - x0, x4, x2, x6, x7, x3, x5, x1 = x0, x1, x2, x3, x4, x5, x6, x7 (paper's form) |
| // - x0, x1, x2, x3, x4, x5, x6, x7 = x0, x7, x2, x5, x1, x6, x3, x4 (sorted by LHS) |
| // The code below uses the second form to make it easier to merge adjacent stores. |
| // (Note that unlike in recursive FFT implementations, the permutation here is |
| // not always mapping indexes to their bit reversals.) |
| // |
| // As written above, the rotation requires four multiplications, but it can be |
| // reduced to three by refactoring (see [dctBox] below), and the scaling in |
| // stage 3 can be merged into the rotation constants, so the overall cost |
| // of a 1D FDCT is 11 multiplies. |
| // |
| // The 1D inverse DCT (IDCT) is the 1D FDCT run backward |
| // with all the basic operations inverted. |
| |
| // dctBox implements a 3-multiply, 3-add rotation+scaling. |
| // Given x0, x1, k*cos θ, and k*sin θ, dctBox returns the |
| // rotated and scaled coordinates. |
| // (It is called dctBox because the rotate+scale operation |
| // is drawn as a box in Figures 1 and 2 in the paper.) |
| func dctBox(x0, x1, kcos, ksin int32) (y0, y1 int32) { |
| // y0 = x0*kcos + x1*ksin |
| // y1 = -x0*ksin + x1*kcos |
| ksum := kcos * (x0 + x1) |
| y0 = ksum + (ksin-kcos)*x1 |
| y1 = ksum - (kcos+ksin)*x0 |
| return y0, y1 |
| } |
| |
| // A block is an 8x8 input to a 2D DCT (either the FDCT or IDCT). |
| // The input is actually only 8x8 uint8 values, and the outputs are 8x8 int16, |
| // but it is convenient to use int32s for intermediate storage, |
| // so we define only a single block type of [8*8]int32. |
| // |
| // A 2D DCT is implemented as 1D DCTs over the rows and columns. |
| // |
| // dct_test.go defines a String method for nice printing in tests. |
| type block [blockSize]int32 |
| |
| const blockSize = 8 * 8 |
| |
| // Note on Numerical Precision |
| // |
| // The inputs to both the FDCT and IDCT are uint8 values stored in a block, |
| // and the outputs are int16s in the same block, but the overall operation |
| // uses int32 values as fixed-point intermediate values. |
| // In the code comments below, the notation “QN.M” refers to a |
| // signed value of 1+N+M significant bits, one of which is the sign bit, |
| // and M of which hold fractional (sub-integer) precision. |
| // For example, 255 as a Q8.0 value is stored as int32(255), |
| // while 255 as a Q8.1 value is stored as int32(510), |
| // and 255.5 as a Q8.1 value is int32(511). |
| // The notation UQN.M refers to an unsigned value of N+M significant bits. |
| // See https://en.wikipedia.org/wiki/Q_(number_format) for more. |
| // |
| // In general we only need to keep about 16 significant bits, but it is more |
| // efficient and somewhat more precise to let unnecessary fractional bits |
| // accumulate and shift them away in bulk rather than after every operation. |
| // As such, it is important to keep track of the number of fractional bits |
| // in each variable at different points in the code, to avoid mistakes like |
| // adding numbers with different fractional precisions, as well as to keep |
| // track of the total number of bits, to avoid overflow. A comment like: |
| // |
| // // x[123] now Q8.2. |
| // |
| // means that x1, x2, and x3 are all Q8.2 (11-bit) values. |
| // Keeping extra precision bits also reduces the size of the errors introduced |
| // by using right shift to approximate rounded division. |
| |
| // Constants needed for the implementation. |
| // These are all 60-bit precision fixed-point constants. |
| // The function c(val, b) rounds the constant to b bits. |
| // c is simple enough that calls to it with constant args |
| // are inlined and constant-propagated down to an inline constant. |
| // Each constant is commented with its Ivy definition (see robpike.io/ivy), |
| // using this scaling helper function: |
| // |
| // op fix x = floor 0.5 + x * 2**60 |
| const ( |
| cos1 = 1130768441178740757 // fix cos 1*pi/16 |
| sin1 = 224923827593068887 // fix sin 1*pi/16 |
| cos3 = 958619196450722178 // fix cos 3*pi/16 |
| sin3 = 640528868967736374 // fix sin 3*pi/16 |
| sqrt2 = 1630477228166597777 // fix sqrt 2 |
| sqrt2_cos6 = 623956622067911264 // fix (sqrt 2)*cos 6*pi/16 |
| sqrt2_sin6 = 1506364539328854985 // fix (sqrt 2)*sin 6*pi/16 |
| sqrt2inv = 815238614083298888 // fix 1/sqrt 2 |
| sqrt2inv_cos6 = 311978311033955632 // fix (1/sqrt 2)*cos 6*pi/16 |
| sqrt2inv_sin6 = 753182269664427492 // fix (1/sqrt 2)*sin 6*pi/16 |
| ) |
| |
| func c(x uint64, bits int) int32 { |
| return int32((x + (1 << (59 - bits))) >> (60 - bits)) |
| } |
| |
| // fdct implements the forward DCT. |
| // Inputs are UQ8.0; outputs are Q13.0. |
| func fdct(b *block) { |
| fdctCols(b) |
| fdctRows(b) |
| } |
| |
| // fdctCols applies the 1D DCT to the columns of b. |
| // Inputs are UQ8.0 in [0,255] but interpreted as [-128,127]. |
| // Outputs are Q10.18. |
| func fdctCols(b *block) { |
| for i := range 8 { |
| x0 := b[0*8+i] |
| x1 := b[1*8+i] |
| x2 := b[2*8+i] |
| x3 := b[3*8+i] |
| x4 := b[4*8+i] |
| x5 := b[5*8+i] |
| x6 := b[6*8+i] |
| x7 := b[7*8+i] |
| |
| // x[01234567] are UQ8.0 in [0,255]. |
| |
| // Stage 1: four butterflies. |
| // In general a butterfly of QN.M inputs produces Q(N+1).M outputs. |
| // A butterfly of UQN.M inputs produces a UQ(N+1).M sum and a QN.M difference. |
| |
| x0, x7 = x0+x7, x0-x7 |
| x1, x6 = x1+x6, x1-x6 |
| x2, x5 = x2+x5, x2-x5 |
| x3, x4 = x3+x4, x3-x4 |
| // x[0123] now UQ9.0 in [0, 510]. |
| // x[4567] now Q8.0 in [-255,255]. |
| |
| // Stage 2: two boxes and two butterflies. |
| // A box on QN.M inputs with B-bit constants |
| // produces Q(N+1).(M+B) outputs. |
| // (The +1 is from the addition.) |
| |
| x4, x7 = dctBox(x4, x7, c(cos3, 18), c(sin3, 18)) |
| x5, x6 = dctBox(x5, x6, c(cos1, 18), c(sin1, 18)) |
| // x[47] now Q9.18 in [-354, 354]. |
| // x[56] now Q9.18 in [-300, 300]. |
| |
| x0, x3 = x0+x3, x0-x3 |
| x1, x2 = x1+x2, x1-x2 |
| // x[01] now UQ10.0 in [0, 1020]. |
| // x[23] now Q9.0 in [-510, 510]. |
| |
| // Stage 3: one box and three butterflies. |
| |
| x2, x3 = dctBox(x2, x3, c(sqrt2_cos6, 18), c(sqrt2_sin6, 18)) |
| // x[23] now Q10.18 in [-943, 943]. |
| |
| x0, x1 = x0+x1, x0-x1 |
| // x0 now UQ11.0 in [0, 2040]. |
| // x1 now Q10.0 in [-1020, 1020]. |
| |
| // Store x0, x1, x2, x3 to their permuted targets. |
| // The original +128 in every input value |
| // has cancelled out except in the “DC signal” x0. |
| // Subtracting 128*8 here is equivalent to subtracting 128 |
| // from every input before we started, but cheaper. |
| // It also converts x0 from UQ11.18 to Q10.18. |
| b[0*8+i] = (x0 - 128*8) << 18 |
| b[4*8+i] = x1 << 18 |
| b[2*8+i] = x2 |
| b[6*8+i] = x3 |
| |
| x4, x6 = x4+x6, x4-x6 |
| x7, x5 = x7+x5, x7-x5 |
| // x[4567] now Q10.18 in [-654, 654]. |
| |
| // Stage 4: two √2 scalings and one butterfly. |
| |
| x5 = (x5 >> 12) * c(sqrt2, 12) |
| x6 = (x6 >> 12) * c(sqrt2, 12) |
| // x[56] still Q10.18 in [-925, 925] (= 654√2). |
| x7, x4 = x7+x4, x7-x4 |
| // x[47] still Q10.18 in [-925, 925] (not Q11.18!). |
| // This is not obvious at all! See “Note on 925” below. |
| |
| // Store x4 x5 x6 x7 to their permuted targets. |
| b[1*8+i] = x7 |
| b[3*8+i] = x5 |
| b[5*8+i] = x6 |
| b[7*8+i] = x4 |
| } |
| } |
| |
| // fdctRows applies the 1D DCT to the rows of b. |
| // Inputs are Q10.18; outputs are Q13.0. |
| func fdctRows(b *block) { |
| for i := range 8 { |
| x := b[8*i : 8*i+8 : 8*i+8] |
| x0 := x[0] |
| x1 := x[1] |
| x2 := x[2] |
| x3 := x[3] |
| x4 := x[4] |
| x5 := x[5] |
| x6 := x[6] |
| x7 := x[7] |
| |
| // x[01234567] are Q10.18 [-1020, 1020]. |
| |
| // Stage 1: four butterflies. |
| |
| x0, x7 = x0+x7, x0-x7 |
| x1, x6 = x1+x6, x1-x6 |
| x2, x5 = x2+x5, x2-x5 |
| x3, x4 = x3+x4, x3-x4 |
| // x[01234567] now Q11.18 in [-2040, 2040]. |
| |
| // Stage 2: two boxes and two butterflies. |
| |
| x4, x7 = dctBox(x4>>14, x7>>14, c(cos3, 14), c(sin3, 14)) |
| x5, x6 = dctBox(x5>>14, x6>>14, c(cos1, 14), c(sin1, 14)) |
| // x[47] now Q12.18 in [-2830, 2830]. |
| // x[56] now Q12.18 in [-2400, 2400]. |
| x0, x3 = x0+x3, x0-x3 |
| x1, x2 = x1+x2, x1-x2 |
| // x[01234567] now Q12.18 in [-4080, 4080]. |
| |
| // Stage 3: one box and three butterflies. |
| |
| x2, x3 = dctBox(x2>>14, x3>>14, c(sqrt2_cos6, 14), c(sqrt2_sin6, 14)) |
| // x[23] now Q13.18 in [-7539, 7539]. |
| x0, x1 = x0+x1, x0-x1 |
| // x[01] now Q13.18 in [-8160, 8160]. |
| x4, x6 = x4+x6, x4-x6 |
| x7, x5 = x7+x5, x7-x5 |
| // x[4567] now Q13.18 in [-5230, 5230]. |
| |
| // Stage 4: two √2 scalings and one butterfly. |
| |
| x5 = (x5 >> 14) * c(sqrt2, 14) |
| x6 = (x6 >> 14) * c(sqrt2, 14) |
| // x[56] still Q13.18 in [-7397, 7397] (= 5230√2). |
| x7, x4 = x7+x4, x7-x4 |
| // x[47] still Q13.18 in [-7395, 7395] (= 2040*3.6246). |
| // See “Note on 925” below. |
| |
| // Cut from Q13.18 to Q13.0. |
| x0 = (x0 + 1<<17) >> 18 |
| x1 = (x1 + 1<<17) >> 18 |
| x2 = (x2 + 1<<17) >> 18 |
| x3 = (x3 + 1<<17) >> 18 |
| x4 = (x4 + 1<<17) >> 18 |
| x5 = (x5 + 1<<17) >> 18 |
| x6 = (x6 + 1<<17) >> 18 |
| x7 = (x7 + 1<<17) >> 18 |
| |
| // Note: Unlike in fdctCols, saved all stores for the end |
| // because they are adjacent memory locations and some systems |
| // can use multiword stores. |
| x[0] = x0 |
| x[1] = x7 |
| x[2] = x2 |
| x[3] = x5 |
| x[4] = x1 |
| x[5] = x6 |
| x[6] = x3 |
| x[7] = x4 |
| } |
| } |
| |
| // “Note on 925”, deferred from above to avoid interrupting code. |
| // |
| // In fdctCols, heading into stage 2, the values x4, x5, x6, x7 are in [-255, 255]. |
| // Let's call those specific values b4, b5, b6, b7, and trace how x[4567] evolve: |
| // |
| // Stage 2: |
| // x4 = b4*cos3 + b7*sin3 |
| // x7 = -b4*sin3 + b7*cos3 |
| // x5 = b5*cos1 + b6*sin1 |
| // x6 = -b5*sin1 + b6*cos1 |
| // |
| // Stage 3: |
| // |
| // x4 = x4+x6 = b4*cos3 + b7*sin3 - b5*sin1 + b6*cos1 |
| // x6 = x4-x6 = b4*cos3 + b7*sin3 + b5*sin1 - b6*cos1 |
| // x7 = x7+x5 = -b4*sin3 + b7*cos3 + b5*cos1 + b6*sin1 |
| // x5 = x7-x5 = -b4*sin3 + b7*cos3 - b5*cos1 - b6*sin1 |
| // |
| // Stage 4: |
| // |
| // x7 = x7+x4 = -b4*sin3 + b7*cos3 + b5*cos1 + b6*sin1 + b4*cos3 + b7*sin3 - b5*sin1 + b6*cos1 |
| // = b4*(cos3-sin3) + b5*(cos1-sin1) + b6*(cos1+sin1) + b7*(cos3+sin3) |
| // < 255*(0.2759 + 0.7857 + 1.1759 + 1.3871) = 255*3.6246 < 925. |
| // |
| // x4 = x7-x4 = -b4*sin3 + b7*cos3 + b5*cos1 + b6*sin1 - b4*cos3 - b7*sin3 + b5*sin1 - b6*cos1 |
| // = -b4*(cos3+sin3) + b5*(cos1+sin1) + b6*(sin1-cos1) + b7*(cos3-sin3) |
| // < same 925. |
| // |
| // The fact that x5, x6 are also at most 925 is not a coincidence: we are computing |
| // the same kinds of numbers for all four, just with different paths to them. |
| // |
| // In fdctRows, the same analysis applies, but the initial values are |
| // in [-2040, 2040] instead of [-255, 255], so the bound is 2040*3.6246 < 7395. |
| |
| // idct implements the inverse DCT. |
| // Inputs are UQ8.0; outputs are Q10.3. |
| func idct(b *block) { |
| // A 2D IDCT is a 1D IDCT on rows followed by columns. |
| idctRows(b) |
| idctCols(b) |
| } |
| |
| // idctRows applies the 1D IDCT to the rows of b. |
| // Inputs are UQ8.0; outputs are Q9.20. |
| func idctRows(b *block) { |
| for i := range 8 { |
| x := b[8*i : 8*i+8 : 8*i+8] |
| x0 := x[0] |
| x7 := x[1] |
| x2 := x[2] |
| x5 := x[3] |
| x1 := x[4] |
| x6 := x[5] |
| x3 := x[6] |
| x4 := x[7] |
| |
| // Run FDCT backward. |
| // Independent operations have been reordered somewhat |
| // to make precision tracking easier. |
| // |
| // Note that “x0, x1 = x0+x1, x0-x1” is now a reverse butterfly |
| // and carries with it an implicit divide by two: the extra bit |
| // is added to the precision, not the value size. |
| |
| // x[01234567] are UQ8.0 in [0, 255]. |
| |
| // Stages 4, 3, 2: x0, x1, x2, x3. |
| |
| x0 <<= 17 |
| x1 <<= 17 |
| // x0, x1 now UQ8.17. |
| x0, x1 = x0+x1, x0-x1 |
| // x0 now UQ8.18 in [0, 255]. |
| // x1 now Q7.18 in [-127½, 127½]. |
| |
| // Note: (1/sqrt 2)*((cos 6*pi/16)+(sin 6*pi/16)) < 0.924, so no new high bit. |
| x2, x3 = dctBox(x2, x3, c(sqrt2inv_cos6, 18), -c(sqrt2inv_sin6, 18)) |
| // x[23] now Q8.18 in [-236, 236]. |
| x1, x2 = x1+x2, x1-x2 |
| x0, x3 = x0+x3, x0-x3 |
| // x[0123] now Q8.19 in [-246, 246]. |
| |
| // Stages 4, 3, 2: x4, x5, x6, x7. |
| |
| x4 <<= 7 |
| x7 <<= 7 |
| // x[47] now UQ8.7 |
| x7, x4 = x7+x4, x7-x4 |
| // x7 now UQ8.8 in [0, 255]. |
| // x4 now Q7.8 in [-127½, 127½]. |
| |
| x6 = x6 * c(sqrt2inv, 8) |
| x5 = x5 * c(sqrt2inv, 8) |
| // x[56] now UQ8.8 in [0, 181]. |
| // Note that 1/√2 has five 0s in its binary representation after |
| // the 8th bit, so this multipliy is actually producing 12 bits of precision. |
| |
| x7, x5 = x7+x5, x7-x5 |
| x4, x6 = x4+x6, x4-x6 |
| // x[4567] now Q8.9 in [-218, 218]. |
| |
| x4, x7 = dctBox(x4>>2, x7>>2, c(cos3, 12), -c(sin3, 12)) |
| x5, x6 = dctBox(x5>>2, x6>>2, c(cos1, 12), -c(sin1, 12)) |
| // x[4567] now Q9.19 in [-303, 303]. |
| |
| // Stage 1. |
| |
| x0, x7 = x0+x7, x0-x7 |
| x1, x6 = x1+x6, x1-x6 |
| x2, x5 = x2+x5, x2-x5 |
| x3, x4 = x3+x4, x3-x4 |
| // x[01234567] now Q9.20 in [-275, 275]. |
| |
| // Note: we don't need all 20 bits of “precision”, |
| // but it is faster to let idctCols shift it away as part |
| // of other operations rather than downshift here. |
| |
| x[0] = x0 |
| x[1] = x1 |
| x[2] = x2 |
| x[3] = x3 |
| x[4] = x4 |
| x[5] = x5 |
| x[6] = x6 |
| x[7] = x7 |
| } |
| } |
| |
| // idctCols applies the 1D IDCT to the columns of b. |
| // Inputs are Q9.20. |
| // Outputs are Q10.3. That is, the result is the IDCT*8. |
| func idctCols(b *block) { |
| for i := range 8 { |
| x0 := b[0*8+i] |
| x7 := b[1*8+i] |
| x2 := b[2*8+i] |
| x5 := b[3*8+i] |
| x1 := b[4*8+i] |
| x6 := b[5*8+i] |
| x3 := b[6*8+i] |
| x4 := b[7*8+i] |
| |
| // x[012345678] are Q9.20. |
| |
| // Start by adding 0.5 to x0 (the incoming DC signal). |
| // The butterflies will add it to all the other values, |
| // and then the final shifts will round properly. |
| x0 += 1 << 19 |
| |
| // Stages 4, 3, 2: x0, x1, x2, x3. |
| |
| x0, x1 = (x0+x1)>>2, (x0-x1)>>2 |
| // x[01] now Q9.19. |
| // Note: (1/sqrt 2)*((cos 6*pi/16)+(sin 6*pi/16)) < 1, so no new high bit. |
| x2, x3 = dctBox(x2>>13, x3>>13, c(sqrt2inv_cos6, 12), -c(sqrt2inv_sin6, 12)) |
| // x[0123] now Q9.19. |
| |
| x1, x2 = x1+x2, x1-x2 |
| x0, x3 = x0+x3, x0-x3 |
| // x[0123] now Q9.20. |
| |
| // Stages 4, 3, 2: x4, x5, x6, x7. |
| |
| x7, x4 = x7+x4, x7-x4 |
| // x[47] now Q9.21. |
| |
| x5 = (x5 >> 13) * c(sqrt2inv, 14) |
| x6 = (x6 >> 13) * c(sqrt2inv, 14) |
| // x[56] now Q9.21. |
| |
| x7, x5 = x7+x5, x7-x5 |
| x4, x6 = x4+x6, x4-x6 |
| // x[4567] now Q9.22. |
| |
| x4, x7 = dctBox(x4>>14, x7>>14, c(cos3, 12), -c(sin3, 12)) |
| x5, x6 = dctBox(x5>>14, x6>>14, c(cos1, 12), -c(sin1, 12)) |
| // x[4567] now Q10.20. |
| |
| x0, x7 = x0+x7, x0-x7 |
| x1, x6 = x1+x6, x1-x6 |
| x2, x5 = x2+x5, x2-x5 |
| x3, x4 = x3+x4, x3-x4 |
| // x[01234567] now Q10.21. |
| |
| x0 >>= 18 |
| x1 >>= 18 |
| x2 >>= 18 |
| x3 >>= 18 |
| x4 >>= 18 |
| x5 >>= 18 |
| x6 >>= 18 |
| x7 >>= 18 |
| // x[01234567] now Q10.3. |
| |
| b[0*8+i] = x0 |
| b[1*8+i] = x1 |
| b[2*8+i] = x2 |
| b[3*8+i] = x3 |
| b[4*8+i] = x4 |
| b[5*8+i] = x5 |
| b[6*8+i] = x6 |
| b[7*8+i] = x7 |
| } |
| } |