@@ -7,6 +7,7 @@ package jpeg
7
7
import (
8
8
"fmt"
9
9
"math"
10
+ "math/big"
10
11
"math/rand"
11
12
"strings"
12
13
"testing"
@@ -29,11 +30,37 @@ func BenchmarkIDCT(b *testing.B) {
29
30
benchmarkDCT (b , idct )
30
31
}
31
32
33
+ const testSlowVsBig = true
34
+
32
35
func TestDCT (t * testing.T ) {
33
36
blocks := make ([]block , len (testBlocks ))
34
37
copy (blocks , testBlocks [:])
35
38
36
- // Append some randomly generated blocks of varying sparseness.
39
+ // All zeros
40
+ blocks = append (blocks , block {})
41
+
42
+ // Every possible unit impulse.
43
+ for i := range blockSize {
44
+ var b block
45
+ b [i ] = 255
46
+ blocks = append (blocks , b )
47
+ }
48
+
49
+ // All ones.
50
+ var ones block
51
+ for i := range ones {
52
+ ones [i ] = 255
53
+ }
54
+ blocks = append (blocks , ones )
55
+
56
+ // Every possible inverted unit impulse.
57
+ for i := range blockSize {
58
+ ones [i ] = 0
59
+ blocks = append (blocks , ones )
60
+ ones [i ] = 255
61
+ }
62
+
63
+ // Some randomly generated blocks of varying sparseness.
37
64
r := rand .New (rand .NewSource (123 ))
38
65
for i := 0 ; i < 100 ; i ++ {
39
66
b := block {}
@@ -44,57 +71,84 @@ func TestDCT(t *testing.T) {
44
71
blocks = append (blocks , b )
45
72
}
46
73
47
- // Check that the FDCT and IDCT functions are inverses, after a scale and
48
- // level shift. Scaling reduces the rounding errors in the conversion from
49
- // floats to ints.
50
- for i , b := range blocks {
51
- got , want := b , b
52
- slowFDCT (& got )
53
- slowIDCT (& got )
54
- for j := range got {
55
- got [j ] = got [j ]/ 8 + 128
56
- }
57
- if d := differ (& got , & want , 2 ); d >= 0 {
58
- t .Errorf ("i=%d: IDCT(FDCT) (diff at %d,%d)\n src\n %s\n got\n %s\n want\n %s\n " , i , d / 8 , d % 8 , & b , & got , & want )
74
+ // Check that the slow FDCT and IDCT functions are inverses,
75
+ // after a scale and level shift.
76
+ // Scaling reduces the rounding errors in the conversion.
77
+ // The “fast” ones are not inverses because the fast IDCT
78
+ // is optimized for 8-bit inputs, not full 16-bit ones.
79
+ slowRoundTrip := func (b * block ) {
80
+ slowFDCT (b )
81
+ slowIDCT (b )
82
+ for j := range b {
83
+ b [j ] = b [j ]/ 8 + 128
59
84
}
60
85
}
86
+ nop := func (* block ) {}
87
+ testDCT (t , "IDCT(FDCT)" , blocks , slowRoundTrip , nop , 1 , 8 )
61
88
62
- // Check that the optimized and slow FDCT implementations agree.
63
- // The fdct function already does a scale and level shift.
64
- for i , b := range blocks {
65
- got , want := b , b
66
- fdct (& got )
67
- slowFDCT (& want )
68
- if d := differ (& got , & want , 2 ); d >= 0 {
69
- t .Errorf ("i=%d: FDCT (diff at %d,%d)\n src\n %s\n got\n %s\n want\n %s\n " , i , d / 8 , d % 8 , & b , & got , & want )
70
- }
89
+ if testSlowVsBig {
90
+ testDCT (t , "slowFDCT" , blocks , slowFDCT , slowerFDCT , 0 , 64 )
91
+ testDCT (t , "slowIDCT" , blocks , slowIDCT , slowerIDCT , 0 , 64 )
71
92
}
72
93
73
- // Check that the optimized and slow IDCT implementations agree.
74
- for i , b := range blocks {
75
- got , want := b , b
76
- idct (& got )
77
- slowIDCT (& want )
78
- if d := differ (& got , & want , 2 ); d >= 0 {
79
- t .Errorf ("i=%d: IDCT (diff at %d,%d)\n src\n %s\n got\n %s\n want\n %s\n " , i , d / 8 , d % 8 , & b , & got , & want )
94
+ // Check that the optimized and slow FDCT implementations agree.
95
+ testDCT (t , "FDCT" , blocks , fdct , slowFDCT , 1 , 16 )
96
+ testDCT (t , "IDCT" , blocks , idct , slowIDCT , 1 , 8 )
97
+ }
98
+
99
+ func testDCT (t * testing.T , name string , blocks []block , fhave , fwant func (* block ), tolerance int32 , maxCloseCalls int ) {
100
+ t .Run (name , func (t * testing.T ) {
101
+ totalClose := 0
102
+ for i , b := range blocks {
103
+ have , want := b , b
104
+ fhave (& have )
105
+ fwant (& want )
106
+ d , n := differ (& have , & want , tolerance )
107
+ if d >= 0 || n > maxCloseCalls {
108
+ fail := ""
109
+ if d >= 0 {
110
+ fail = fmt .Sprintf ("diff at %d,%d" , d / 8 , d % 8 )
111
+ }
112
+ if n > maxCloseCalls {
113
+ if fail != "" {
114
+ fail += "; "
115
+ }
116
+ fail += fmt .Sprintf ("%d close calls" , n )
117
+ }
118
+ t .Errorf ("i=%d: %s (%s)\n src\n %s\n have\n %s\n want\n %s\n " ,
119
+ i , name , fail , & b , & have , & want )
120
+ }
121
+ totalClose += n
80
122
}
81
- }
123
+ if tolerance > 0 {
124
+ t .Logf ("%d/%d total close calls" , totalClose , len (blocks )* blockSize )
125
+ }
126
+ })
82
127
}
83
128
84
- // differ reports whether any pair-wise elements in b0 and b1 differ by more than 'ok'.
85
- // That tolerance is because there isn't a single definitive decoding of
86
- // a given JPEG image, even before the YCbCr to RGB conversion; implementations
129
+ // differ returns the index of the first pair-wise elements in b0 and b1
130
+ // that differ by more than 'ok', along with the total number of elements
131
+ // that differ by at least ok ("close calls").
132
+ //
133
+ // There isn't a single definitive decoding of a given JPEG image,
134
+ // even before the YCbCr to RGB conversion; implementations
87
135
// can have different IDCT rounding errors.
88
- // If there is a difference, differ returns the index of the first difference.
89
- // Otherwise it returns -1.
90
- func differ (b0 , b1 * block , ok int32 ) int {
136
+ //
137
+ // If there are no differences, differ returns -1, 0.
138
+ func differ (b0 , b1 * block , ok int32 ) (index , closeCalls int ) {
139
+ index = - 1
91
140
for i := range b0 {
92
141
delta := b0 [i ] - b1 [i ]
93
142
if delta < - ok || ok < delta {
94
- return i
143
+ if index < 0 {
144
+ index = i
145
+ }
146
+ }
147
+ if delta <= - ok || ok <= delta {
148
+ closeCalls ++
95
149
}
96
150
}
97
- return - 1
151
+ return
98
152
}
99
153
100
154
// alpha returns 1 if i is 0 and returns √2 otherwise.
@@ -105,6 +159,14 @@ func alpha(i int) float64 {
105
159
return math .Sqrt2
106
160
}
107
161
162
+ // bigAlpha returns 1 if i is 0 and returns √2 otherwise.
163
+ func bigAlpha (i int ) * big.Float {
164
+ if i == 0 {
165
+ return bigFloat1
166
+ }
167
+ return bigFloatSqrt2
168
+ }
169
+
108
170
var cosines = [32 ]float64 {
109
171
+ 1.0000000000000000000000000000000000000000000000000000000000000000 , // cos(π/16 * 0)
110
172
+ 0.9807852804032304491261822361342390369739337308933360950029160885 , // cos(π/16 * 1)
@@ -143,6 +205,57 @@ var cosines = [32]float64{
143
205
+ 0.9807852804032304491261822361342390369739337308933360950029160885 , // cos(π/16 * 31)
144
206
}
145
207
208
+ func bigFloat (s string ) * big.Float {
209
+ f , ok := new (big.Float ).SetString (s )
210
+ if ! ok {
211
+ panic ("bad float" )
212
+ }
213
+ return f
214
+ }
215
+
216
+ var (
217
+ bigFloat1 = big .NewFloat (1 )
218
+ bigFloatSqrt2 = bigFloat ("1.41421356237309504880168872420969807856967187537694807317667974" )
219
+ )
220
+
221
+ var bigCosines = [32 ]* big.Float {
222
+ bigFloat ("+1.0000000000000000000000000000000000000000000000000000000000000000" ), // cos(π/16 * 0)
223
+ bigFloat ("+0.9807852804032304491261822361342390369739337308933360950029160885" ), // cos(π/16 * 1)
224
+ bigFloat ("+0.9238795325112867561281831893967882868224166258636424861150977312" ), // cos(π/16 * 2)
225
+ bigFloat ("+0.8314696123025452370787883776179057567385608119872499634461245902" ), // cos(π/16 * 3)
226
+ bigFloat ("+0.7071067811865475244008443621048490392848359376884740365883398689" ), // cos(π/16 * 4)
227
+ bigFloat ("+0.5555702330196022247428308139485328743749371907548040459241535282" ), // cos(π/16 * 5)
228
+ bigFloat ("+0.3826834323650897717284599840303988667613445624856270414338006356" ), // cos(π/16 * 6)
229
+ bigFloat ("+0.1950903220161282678482848684770222409276916177519548077545020894" ), // cos(π/16 * 7)
230
+
231
+ bigFloat ("-0.0000000000000000000000000000000000000000000000000000000000000000" ), // cos(π/16 * 8)
232
+ bigFloat ("-0.1950903220161282678482848684770222409276916177519548077545020894" ), // cos(π/16 * 9)
233
+ bigFloat ("-0.3826834323650897717284599840303988667613445624856270414338006356" ), // cos(π/16 * 10)
234
+ bigFloat ("-0.5555702330196022247428308139485328743749371907548040459241535282" ), // cos(π/16 * 11)
235
+ bigFloat ("-0.7071067811865475244008443621048490392848359376884740365883398689" ), // cos(π/16 * 12)
236
+ bigFloat ("-0.8314696123025452370787883776179057567385608119872499634461245902" ), // cos(π/16 * 13)
237
+ bigFloat ("-0.9238795325112867561281831893967882868224166258636424861150977312" ), // cos(π/16 * 14)
238
+ bigFloat ("-0.9807852804032304491261822361342390369739337308933360950029160885" ), // cos(π/16 * 15)
239
+
240
+ bigFloat ("-1.0000000000000000000000000000000000000000000000000000000000000000" ), // cos(π/16 * 16)
241
+ bigFloat ("-0.9807852804032304491261822361342390369739337308933360950029160885" ), // cos(π/16 * 17)
242
+ bigFloat ("-0.9238795325112867561281831893967882868224166258636424861150977312" ), // cos(π/16 * 18)
243
+ bigFloat ("-0.8314696123025452370787883776179057567385608119872499634461245902" ), // cos(π/16 * 19)
244
+ bigFloat ("-0.7071067811865475244008443621048490392848359376884740365883398689" ), // cos(π/16 * 20)
245
+ bigFloat ("-0.5555702330196022247428308139485328743749371907548040459241535282" ), // cos(π/16 * 21)
246
+ bigFloat ("-0.3826834323650897717284599840303988667613445624856270414338006356" ), // cos(π/16 * 22)
247
+ bigFloat ("-0.1950903220161282678482848684770222409276916177519548077545020894" ), // cos(π/16 * 23)
248
+
249
+ bigFloat ("+0.0000000000000000000000000000000000000000000000000000000000000000" ), // cos(π/16 * 24)
250
+ bigFloat ("+0.1950903220161282678482848684770222409276916177519548077545020894" ), // cos(π/16 * 25)
251
+ bigFloat ("+0.3826834323650897717284599840303988667613445624856270414338006356" ), // cos(π/16 * 26)
252
+ bigFloat ("+0.5555702330196022247428308139485328743749371907548040459241535282" ), // cos(π/16 * 27)
253
+ bigFloat ("+0.7071067811865475244008443621048490392848359376884740365883398689" ), // cos(π/16 * 28)
254
+ bigFloat ("+0.8314696123025452370787883776179057567385608119872499634461245902" ), // cos(π/16 * 29)
255
+ bigFloat ("+0.9238795325112867561281831893967882868224166258636424861150977312" ), // cos(π/16 * 30)
256
+ bigFloat ("+0.9807852804032304491261822361342390369739337308933360950029160885" ), // cos(π/16 * 31)
257
+ }
258
+
146
259
// slowFDCT performs the 8*8 2-dimensional forward discrete cosine transform:
147
260
//
148
261
// dst[u,v] = (1/8) * Σ_x Σ_y alpha(u) * alpha(v) * src[x,y] *
@@ -153,7 +266,7 @@ var cosines = [32]float64{
153
266
//
154
267
// b acts as both dst and src.
155
268
func slowFDCT (b * block ) {
156
- var dst [ blockSize ] float64
269
+ var dst block
157
270
for v := 0 ; v < 8 ; v ++ {
158
271
for u := 0 ; u < 8 ; u ++ {
159
272
sum := 0.0
@@ -164,13 +277,40 @@ func slowFDCT(b *block) {
164
277
cosines [((2 * y + 1 )* v )% 32 ]
165
278
}
166
279
}
167
- dst [8 * v + u ] = sum
280
+ dst [8 * v + u ] = int32 ( math . Round ( sum ))
168
281
}
169
282
}
170
- // Convert from float64 to int32.
171
- for i := range dst {
172
- b [i ] = int32 (dst [i ] + 0.5 )
283
+ * b = dst
284
+ }
285
+
286
+ // slowerFDCT is slowFDCT but using big.Floats to validate slowFDCT.
287
+ func slowerFDCT (b * block ) {
288
+ var dst block
289
+ for v := 0 ; v < 8 ; v ++ {
290
+ for u := 0 ; u < 8 ; u ++ {
291
+ sum := big .NewFloat (0 )
292
+ for y := 0 ; y < 8 ; y ++ {
293
+ for x := 0 ; x < 8 ; x ++ {
294
+ f := big .NewFloat (float64 (b [8 * y + x ] - 128 ))
295
+ f = new (big.Float ).Mul (f , bigAlpha (u ))
296
+ f = new (big.Float ).Mul (f , bigAlpha (v ))
297
+ f = new (big.Float ).Mul (f , bigCosines [((2 * x + 1 )* u )% 32 ])
298
+ f = new (big.Float ).Mul (f , bigCosines [((2 * y + 1 )* v )% 32 ])
299
+ sum = new (big.Float ).Add (sum , f )
300
+ }
301
+ }
302
+ // Int64 truncates toward zero, so add ±0.5
303
+ // as needed to round
304
+ if sum .Sign () > 0 {
305
+ sum = new (big.Float ).Add (sum , big .NewFloat (+ 0.5 ))
306
+ } else {
307
+ sum = new (big.Float ).Add (sum , big .NewFloat (- 0.5 ))
308
+ }
309
+ i , _ := sum .Int64 ()
310
+ dst [8 * v + u ] = int32 (i )
311
+ }
173
312
}
313
+ * b = dst
174
314
}
175
315
176
316
// slowIDCT performs the 8*8 2-dimensional inverse discrete cosine transform:
@@ -183,7 +323,7 @@ func slowFDCT(b *block) {
183
323
//
184
324
// b acts as both dst and src.
185
325
func slowIDCT (b * block ) {
186
- var dst [ blockSize ] float64
326
+ var dst block
187
327
for y := 0 ; y < 8 ; y ++ {
188
328
for x := 0 ; x < 8 ; x ++ {
189
329
sum := 0.0
@@ -194,13 +334,41 @@ func slowIDCT(b *block) {
194
334
cosines [((2 * y + 1 )* v )% 32 ]
195
335
}
196
336
}
197
- dst [8 * y + x ] = sum / 8
337
+ dst [8 * y + x ] = int32 ( math . Round ( sum / 8 ))
198
338
}
199
339
}
200
- // Convert from float64 to int32.
201
- for i := range dst {
202
- b [i ] = int32 (dst [i ] + 0.5 )
340
+ * b = dst
341
+ }
342
+
343
+ // slowerIDCT is slowIDCT but using big.Floats to validate slowIDCT.
344
+ func slowerIDCT (b * block ) {
345
+ var dst block
346
+ for y := 0 ; y < 8 ; y ++ {
347
+ for x := 0 ; x < 8 ; x ++ {
348
+ sum := big .NewFloat (0 )
349
+ for v := 0 ; v < 8 ; v ++ {
350
+ for u := 0 ; u < 8 ; u ++ {
351
+ f := big .NewFloat (float64 (b [8 * v + u ]))
352
+ f = new (big.Float ).Mul (f , bigAlpha (u ))
353
+ f = new (big.Float ).Mul (f , bigAlpha (v ))
354
+ f = new (big.Float ).Mul (f , bigCosines [((2 * x + 1 )* u )% 32 ])
355
+ f = new (big.Float ).Mul (f , bigCosines [((2 * y + 1 )* v )% 32 ])
356
+ f = new (big.Float ).Quo (f , big .NewFloat (8 ))
357
+ sum = new (big.Float ).Add (sum , f )
358
+ }
359
+ }
360
+ // Int64 truncates toward zero, so add ±0.5
361
+ // as needed to round
362
+ if sum .Sign () > 0 {
363
+ sum = new (big.Float ).Add (sum , big .NewFloat (+ 0.5 ))
364
+ } else {
365
+ sum = new (big.Float ).Add (sum , big .NewFloat (- 0.5 ))
366
+ }
367
+ i , _ := sum .Int64 ()
368
+ dst [8 * y + x ] = int32 (i )
369
+ }
203
370
}
371
+ * b = dst
204
372
}
205
373
206
374
func (b * block ) String () string {
0 commit comments