Imported Upstream version 3.0.1
[platform/upstream/libjpeg-turbo.git] / java / TJUnitTest.java
1 /*
2  * Copyright (C)2011-2018, 2022-2023 D. R. Commander.  All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * - Redistributions of source code must retain the above copyright notice,
8  *   this list of conditions and the following disclaimer.
9  * - Redistributions in binary form must reproduce the above copyright notice,
10  *   this list of conditions and the following disclaimer in the documentation
11  *   and/or other materials provided with the distribution.
12  * - Neither the name of the libjpeg-turbo Project nor the names of its
13  *   contributors may be used to endorse or promote products derived from this
14  *   software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 /*
30  * This program tests the various code paths in the TurboJPEG JNI Wrapper
31  */
32
33 import java.io.*;
34 import java.util.*;
35 import java.awt.image.*;
36 import javax.imageio.*;
37 import java.nio.*;
38 import org.libjpegturbo.turbojpeg.*;
39
40 @SuppressWarnings("checkstyle:JavadocType")
41 final class TJUnitTest {
42
43   private TJUnitTest() {}
44
45   static final String CLASS_NAME =
46     new TJUnitTest().getClass().getName();
47
48   static void usage() {
49     System.out.println("\nUSAGE: java " + CLASS_NAME + " [options]\n");
50     System.out.println("Options:");
51     System.out.println("-yuv = test YUV encoding/compression/decompression/decoding");
52     System.out.println("       (8-bit data precision only)");
53     System.out.println("-noyuvpad = do not pad each row in each Y, U, and V plane to the nearest");
54     System.out.println("            multiple of 4 bytes");
55     System.out.println("-precision N = test N-bit data precision (N is 8, 12, or 16; default is 8; if N");
56     System.out.println("               is 16, then -lossless is implied)");
57     System.out.println("-lossless = test lossless JPEG compression/decompression");
58     System.out.println("-bi = test BufferedImage I/O (8-bit data precision only)\n");
59     System.exit(1);
60   }
61
62   static final String[] SUBNAME_LONG = {
63     "4:4:4", "4:2:2", "4:2:0", "GRAY", "4:4:0", "4:1:1", "4:4:1"
64   };
65   static final String[] SUBNAME = {
66     "444", "422", "420", "GRAY", "440", "411", "441"
67   };
68
69   static final String[] PIXFORMATSTR = {
70     "RGB", "BGR", "RGBX", "BGRX", "XBGR", "XRGB", "Grayscale",
71     "RGBA", "BGRA", "ABGR", "ARGB", "CMYK"
72   };
73
74   static final int[] FORMATS_3SAMPLE = {
75     TJ.PF_RGB, TJ.PF_BGR
76   };
77   static final int[] FORMATS_3BYTEBI = {
78     BufferedImage.TYPE_3BYTE_BGR
79   };
80   static final int[] FORMATS_4SAMPLE = {
81     TJ.PF_RGBX, TJ.PF_BGRX, TJ.PF_XBGR, TJ.PF_XRGB, TJ.PF_CMYK
82   };
83   static final int[] FORMATS_4BYTEBI = {
84     BufferedImage.TYPE_INT_BGR, BufferedImage.TYPE_INT_RGB,
85     BufferedImage.TYPE_4BYTE_ABGR, BufferedImage.TYPE_4BYTE_ABGR_PRE,
86     BufferedImage.TYPE_INT_ARGB, BufferedImage.TYPE_INT_ARGB_PRE
87   };
88   static final int[] FORMATS_GRAY = {
89     TJ.PF_GRAY
90   };
91   static final int[] FORMATS_GRAYBI = {
92     BufferedImage.TYPE_BYTE_GRAY
93   };
94   static final int[] FORMATS_RGB = {
95     TJ.PF_RGB
96   };
97
98   private static boolean doYUV = false;
99   private static boolean lossless = false;
100   private static int psv = 1;
101   private static int yuvAlign = 4;
102   private static int precision = 8;
103   private static int sampleSize, maxSample, tolerance, redToY, yellowToY;
104   private static boolean bi = false;
105
106   private static int exitStatus = 0;
107
108   static int biTypePF(int biType) {
109     ByteOrder byteOrder = ByteOrder.nativeOrder();
110     switch (biType) {
111     case BufferedImage.TYPE_3BYTE_BGR:
112       return TJ.PF_BGR;
113     case BufferedImage.TYPE_4BYTE_ABGR:
114     case BufferedImage.TYPE_4BYTE_ABGR_PRE:
115       return TJ.PF_ABGR;
116     case BufferedImage.TYPE_BYTE_GRAY:
117       return TJ.PF_GRAY;
118     case BufferedImage.TYPE_INT_BGR:
119       return TJ.PF_RGBX;
120     case BufferedImage.TYPE_INT_RGB:
121       return TJ.PF_BGRX;
122     case BufferedImage.TYPE_INT_ARGB:
123     case BufferedImage.TYPE_INT_ARGB_PRE:
124       return TJ.PF_BGRA;
125     default:
126       return 0;
127     }
128   }
129
130   static String biTypeStr(int biType) {
131     switch (biType) {
132     case BufferedImage.TYPE_3BYTE_BGR:
133       return "3BYTE_BGR";
134     case BufferedImage.TYPE_4BYTE_ABGR:
135       return "4BYTE_ABGR";
136     case BufferedImage.TYPE_4BYTE_ABGR_PRE:
137       return "4BYTE_ABGR_PRE";
138     case BufferedImage.TYPE_BYTE_GRAY:
139       return "BYTE_GRAY";
140     case BufferedImage.TYPE_INT_BGR:
141       return "INT_BGR";
142     case BufferedImage.TYPE_INT_RGB:
143       return "INT_RGB";
144     case BufferedImage.TYPE_INT_ARGB:
145       return "INT_ARGB";
146     case BufferedImage.TYPE_INT_ARGB_PRE:
147       return "INT_ARGB_PRE";
148     default:
149       return "Unknown";
150     }
151   }
152
153   static void fillArray(Object buf, int val) {
154     if (precision == 8)
155       Arrays.fill((byte[])buf, (byte)val);
156     else
157       Arrays.fill((short[])buf, (short)val);
158   }
159
160   static void setVal(Object buf, int index, int value) {
161     if (precision == 8)
162       ((byte[])buf)[index] = (byte)value;
163     else
164       ((short[])buf)[index] = (short)value;
165   }
166
167   static void initBuf(Object buf, int w, int pitch, int h, int pf,
168                       boolean bottomUp) throws Exception {
169     int roffset = TJ.getRedOffset(pf);
170     int goffset = TJ.getGreenOffset(pf);
171     int boffset = TJ.getBlueOffset(pf);
172     int aoffset = TJ.getAlphaOffset(pf);
173     int ps = TJ.getPixelSize(pf);
174     int index, row, col, halfway = 16;
175
176     if (pf == TJ.PF_GRAY) {
177       fillArray(buf, 0);
178       for (row = 0; row < h; row++) {
179         for (col = 0; col < w; col++) {
180           if (bottomUp)
181             index = pitch * (h - row - 1) + col;
182           else
183             index = pitch * row + col;
184           if (((row / 8) + (col / 8)) % 2 == 0)
185             setVal(buf, index, (row < halfway) ? maxSample : 0);
186           else
187             setVal(buf, index, (row < halfway) ? redToY : yellowToY);
188         }
189       }
190       return;
191     }
192     if (pf == TJ.PF_CMYK) {
193       fillArray(buf, maxSample);
194       for (row = 0; row < h; row++) {
195         for (col = 0; col < w; col++) {
196           if (bottomUp)
197             index = (h - row - 1) * w + col;
198           else
199             index = row * w + col;
200           if (((row / 8) + (col / 8)) % 2 == 0) {
201             if (row >= halfway) setVal(buf, index * ps + 3, 0);
202           } else {
203             setVal(buf, index * ps + 2, 0);
204             if (row < halfway)
205               setVal(buf, index * ps + 1, 0);
206           }
207         }
208       }
209       return;
210     }
211
212     fillArray(buf, 0);
213     for (row = 0; row < h; row++) {
214       for (col = 0; col < w; col++) {
215         if (bottomUp)
216           index = pitch * (h - row - 1) + col * ps;
217         else
218           index = pitch * row + col * ps;
219         if (((row / 8) + (col / 8)) % 2 == 0) {
220           if (row < halfway) {
221             setVal(buf, index + roffset, maxSample);
222             setVal(buf, index + goffset, maxSample);
223             setVal(buf, index + boffset, maxSample);
224           }
225         } else {
226           setVal(buf, index + roffset, maxSample);
227           if (row >= halfway)
228             setVal(buf, index + goffset, maxSample);
229         }
230         if (aoffset >= 0)
231           setVal(buf, index + aoffset, maxSample);
232       }
233     }
234   }
235
236   static void initIntBuf(int[] buf, int w, int pitch, int h, int pf,
237                          boolean bottomUp) throws Exception {
238     int rshift = TJ.getRedOffset(pf) * 8;
239     int gshift = TJ.getGreenOffset(pf) * 8;
240     int bshift = TJ.getBlueOffset(pf) * 8;
241     int ashift = TJ.getAlphaOffset(pf) * 8;
242     int index, row, col, halfway = 16;
243
244     Arrays.fill(buf, 0);
245     for (row = 0; row < h; row++) {
246       for (col = 0; col < w; col++) {
247         if (bottomUp)
248           index = pitch * (h - row - 1) + col;
249         else
250           index = pitch * row + col;
251         if (((row / 8) + (col / 8)) % 2 == 0) {
252           if (row < halfway) {
253             buf[index] |= (255 << rshift);
254             buf[index] |= (255 << gshift);
255             buf[index] |= (255 << bshift);
256           }
257         } else {
258           buf[index] |= (255 << rshift);
259           if (row >= halfway)
260             buf[index] |= (255 << gshift);
261         }
262         if (ashift >= 0)
263           buf[index] |= (255 << ashift);
264       }
265     }
266   }
267
268   static void initImg(BufferedImage img, int pf, boolean bottomUp)
269                       throws Exception {
270     WritableRaster wr = img.getRaster();
271     int imgType = img.getType();
272
273     if (imgType == BufferedImage.TYPE_INT_RGB ||
274         imgType == BufferedImage.TYPE_INT_BGR ||
275         imgType == BufferedImage.TYPE_INT_ARGB ||
276         imgType == BufferedImage.TYPE_INT_ARGB_PRE) {
277       SinglePixelPackedSampleModel sm =
278         (SinglePixelPackedSampleModel)img.getSampleModel();
279       int pitch = sm.getScanlineStride();
280       DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
281       int[] buf = db.getData();
282       initIntBuf(buf, img.getWidth(), pitch, img.getHeight(), pf, bottomUp);
283     } else {
284       ComponentSampleModel sm = (ComponentSampleModel)img.getSampleModel();
285       int pitch = sm.getScanlineStride();
286       DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
287       byte[] buf = db.getData();
288       initBuf(buf, img.getWidth(), pitch, img.getHeight(), pf, bottomUp);
289     }
290   }
291
292   static void checkVal(int row, int col, int v, String vname, int cv)
293                        throws Exception {
294     v = (v < 0) ? v + 256 : v;
295     if (v < cv - tolerance || v > cv + tolerance) {
296       throw new Exception("Comp. " + vname + " at " + row + "," + col +
297                           " should be " + cv + ", not " + v);
298     }
299   }
300
301   static void checkVal0(int row, int col, int v, String vname)
302                         throws Exception {
303     v = (v < 0) ? v + 256 : v;
304     if (v > tolerance) {
305       throw new Exception("Comp. " + vname + " at " + row + "," + col +
306                           " should be 0, not " + v);
307     }
308   }
309
310   static void checkValMax(int row, int col, int v, String vname)
311                           throws Exception {
312     v = (v < 0) ? v + 256 : v;
313     if (v < maxSample - tolerance) {
314       throw new Exception("Comp. " + vname + " at " + row + "," + col +
315                           " should be " + maxSample + ", not " + v);
316     }
317   }
318
319   static int getVal(Object buf, int index) {
320     int v;
321     if (precision == 8)
322       v = (int)(((byte[])buf)[index]);
323     else
324       v = (int)(((short[])buf)[index]);
325     if (v < 0)
326       v += maxSample + 1;
327     return v;
328   }
329
330   static int checkBuf(Object buf, int w, int pitch, int h, int pf, int subsamp,
331                       TJScalingFactor sf, boolean bottomUp) throws Exception {
332     int roffset = TJ.getRedOffset(pf);
333     int goffset = TJ.getGreenOffset(pf);
334     int boffset = TJ.getBlueOffset(pf);
335     int aoffset = TJ.getAlphaOffset(pf);
336     int ps = TJ.getPixelSize(pf);
337     int index, row, col, retval = 1;
338     int halfway = 16 * sf.getNum() / sf.getDenom();
339     int blockSize = 8 * sf.getNum() / sf.getDenom();
340
341     try {
342
343       if (pf == TJ.PF_GRAY)
344         roffset = goffset = boffset = 0;
345
346       if (pf == TJ.PF_CMYK) {
347         for (row = 0; row < h; row++) {
348           for (col = 0; col < w; col++) {
349             if (bottomUp)
350               index = (h - row - 1) * w + col;
351             else
352               index = row * w + col;
353             int c = getVal(buf, index * ps);
354             int m = getVal(buf, index * ps + 1);
355             int y = getVal(buf, index * ps + 2);
356             int k = getVal(buf, index * ps + 3);
357             checkValMax(row, col, c, "C");
358             if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
359               checkValMax(row, col, m, "M");
360               checkValMax(row, col, y, "Y");
361               if (row < halfway)
362                 checkValMax(row, col, k, "K");
363               else
364                 checkVal0(row, col, k, "K");
365             } else {
366               checkVal0(row, col, y, "Y");
367               checkValMax(row, col, k, "K");
368               if (row < halfway)
369                 checkVal0(row, col, m, "M");
370               else
371                 checkValMax(row, col, m, "M");
372             }
373           }
374         }
375         return 1;
376       }
377
378       for (row = 0; row < halfway; row++) {
379         for (col = 0; col < w; col++) {
380           if (bottomUp)
381             index = pitch * (h - row - 1) + col * ps;
382           else
383             index = pitch * row + col * ps;
384           int r = getVal(buf, index + roffset);
385           int g = getVal(buf, index + goffset);
386           int b = getVal(buf, index + boffset);
387           int a = aoffset >= 0 ? getVal(buf, index + aoffset) : maxSample;
388           if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
389             if (row < halfway) {
390               checkValMax(row, col, r, "R");
391               checkValMax(row, col, g, "G");
392               checkValMax(row, col, b, "B");
393             } else {
394               checkVal0(row, col, r, "R");
395               checkVal0(row, col, g, "G");
396               checkVal0(row, col, b, "B");
397             }
398           } else {
399             if (subsamp == TJ.SAMP_GRAY) {
400               if (row < halfway) {
401                 checkVal(row, col, r, "R", redToY);
402                 checkVal(row, col, g, "G", redToY);
403                 checkVal(row, col, b, "B", redToY);
404               } else {
405                 checkVal(row, col, r, "R", yellowToY);
406                 checkVal(row, col, g, "G", yellowToY);
407                 checkVal(row, col, b, "B", yellowToY);
408               }
409             } else {
410               checkValMax(row, col, r, "R");
411               if (row < halfway) {
412                 checkVal0(row, col, g, "G");
413               } else {
414                 checkValMax(row, col, g, "G");
415               }
416               checkVal0(row, col, b, "B");
417             }
418           }
419           checkValMax(row, col, a, "A");
420         }
421       }
422     } catch (Exception e) {
423       System.out.println("\n" + e.getMessage());
424       retval = 0;
425     }
426
427     if (retval == 0) {
428       for (row = 0; row < h; row++) {
429         for (col = 0; col < w; col++) {
430           if (pf == TJ.PF_CMYK) {
431             int c = getVal(buf, pitch * row + col * ps);
432             int m = getVal(buf, pitch * row + col * ps + 1);
433             int y = getVal(buf, pitch * row + col * ps + 2);
434             int k = getVal(buf, pitch * row + col * ps + 3);
435             System.out.format("%3d/%3d/%3d/%3d ", c, m, y, k);
436           } else {
437             int r = getVal(buf, pitch * row + col * ps + roffset);
438             int g = getVal(buf, pitch * row + col * ps + goffset);
439             int b = getVal(buf, pitch * row + col * ps + boffset);
440             System.out.format("%3d/%3d/%3d ", r, g, b);
441           }
442         }
443         System.out.print("\n");
444       }
445     }
446     return retval;
447   }
448
449   static int checkIntBuf(int[] buf, int w, int pitch, int h, int pf,
450                          int subsamp, TJScalingFactor sf, boolean bottomUp)
451                          throws Exception {
452     int rshift = TJ.getRedOffset(pf) * 8;
453     int gshift = TJ.getGreenOffset(pf) * 8;
454     int bshift = TJ.getBlueOffset(pf) * 8;
455     int ashift = TJ.getAlphaOffset(pf) * 8;
456     int index, row, col, retval = 1;
457     int halfway = 16 * sf.getNum() / sf.getDenom();
458     int blockSize = 8 * sf.getNum() / sf.getDenom();
459
460     try {
461       for (row = 0; row < halfway; row++) {
462         for (col = 0; col < w; col++) {
463           if (bottomUp)
464             index = pitch * (h - row - 1) + col;
465           else
466             index = pitch * row + col;
467           int r = (buf[index] >> rshift) & 0xFF;
468           int g = (buf[index] >> gshift) & 0xFF;
469           int b = (buf[index] >> bshift) & 0xFF;
470           int a = ashift >= 0 ? (buf[index] >> ashift) & 0xFF : 255;
471           if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
472             if (row < halfway) {
473               checkValMax(row, col, r, "R");
474               checkValMax(row, col, g, "G");
475               checkValMax(row, col, b, "B");
476             } else {
477               checkVal0(row, col, r, "R");
478               checkVal0(row, col, g, "G");
479               checkVal0(row, col, b, "B");
480             }
481           } else {
482             if (subsamp == TJ.SAMP_GRAY) {
483               if (row < halfway) {
484                 checkVal(row, col, r, "R", 76);
485                 checkVal(row, col, g, "G", 76);
486                 checkVal(row, col, b, "B", 76);
487               } else {
488                 checkVal(row, col, r, "R", 226);
489                 checkVal(row, col, g, "G", 226);
490                 checkVal(row, col, b, "B", 226);
491               }
492             } else {
493               checkValMax(row, col, r, "R");
494               if (row < halfway) {
495                 checkVal0(row, col, g, "G");
496               } else {
497                 checkValMax(row, col, g, "G");
498               }
499               checkVal0(row, col, b, "B");
500             }
501           }
502           checkValMax(row, col, a, "A");
503         }
504       }
505     } catch (Exception e) {
506       System.out.println("\n" + e.getMessage());
507       retval = 0;
508     }
509
510     if (retval == 0) {
511       for (row = 0; row < h; row++) {
512         for (col = 0; col < w; col++) {
513           int r = (buf[pitch * row + col] >> rshift) & 0xFF;
514           int g = (buf[pitch * row + col] >> gshift) & 0xFF;
515           int b = (buf[pitch * row + col] >> bshift) & 0xFF;
516           if (r < 0) r += 256;
517           if (g < 0) g += 256;
518           if (b < 0) b += 256;
519           System.out.format("%3d/%3d/%3d ", r, g, b);
520         }
521         System.out.print("\n");
522       }
523     }
524     return retval;
525   }
526
527   static int checkImg(BufferedImage img, int pf, int subsamp,
528                       TJScalingFactor sf, boolean bottomUp) throws Exception {
529     WritableRaster wr = img.getRaster();
530     int imgType = img.getType();
531     if (imgType == BufferedImage.TYPE_INT_RGB ||
532         imgType == BufferedImage.TYPE_INT_BGR ||
533         imgType == BufferedImage.TYPE_INT_ARGB ||
534         imgType == BufferedImage.TYPE_INT_ARGB_PRE) {
535       SinglePixelPackedSampleModel sm =
536         (SinglePixelPackedSampleModel)img.getSampleModel();
537       int pitch = sm.getScanlineStride();
538       DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
539       int[] buf = db.getData();
540       return checkIntBuf(buf, img.getWidth(), pitch, img.getHeight(), pf,
541                          subsamp, sf, bottomUp);
542     } else {
543       ComponentSampleModel sm = (ComponentSampleModel)img.getSampleModel();
544       int pitch = sm.getScanlineStride();
545       DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
546       byte[] buf = db.getData();
547       return checkBuf(buf, img.getWidth(), pitch, img.getHeight(), pf, subsamp,
548                       sf, bottomUp);
549     }
550   }
551
552   static int pad(int v, int p) {
553     return ((v + (p) - 1) & (~((p) - 1)));
554   }
555
556   static int checkBufYUV(byte[] buf, int size, int w, int h, int subsamp,
557                          TJScalingFactor sf) throws Exception {
558     int row, col;
559     int hsf = TJ.getMCUWidth(subsamp) / 8, vsf = TJ.getMCUHeight(subsamp) / 8;
560     int pw = pad(w, hsf), ph = pad(h, vsf);
561     int cw = pw / hsf, ch = ph / vsf;
562     int ypitch = pad(pw, yuvAlign), uvpitch = pad(cw, yuvAlign);
563     int retval = 1;
564     int correctsize = ypitch * ph +
565                       (subsamp == TJ.SAMP_GRAY ? 0 : uvpitch * ch * 2);
566     int halfway = 16 * sf.getNum() / sf.getDenom();
567     int blockSize = 8 * sf.getNum() / sf.getDenom();
568
569     try {
570       if (size != correctsize)
571         throw new Exception("Incorrect size " + size + ".  Should be " +
572                             correctsize);
573
574       for (row = 0; row < ph; row++) {
575         for (col = 0; col < pw; col++) {
576           byte y = buf[ypitch * row + col];
577           if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
578             if (row < halfway)
579               checkValMax(row, col, y, "Y");
580             else
581               checkVal0(row, col, y, "Y");
582           } else {
583             if (row < halfway)
584               checkVal(row, col, y, "Y", 76);
585             else
586               checkVal(row, col, y, "Y", 226);
587           }
588         }
589       }
590       if (subsamp != TJ.SAMP_GRAY) {
591         halfway = 16 / vsf * sf.getNum() / sf.getDenom();
592         for (row = 0; row < ch; row++) {
593           for (col = 0; col < cw; col++) {
594             byte u = buf[ypitch * ph + (uvpitch * row + col)],
595                  v = buf[ypitch * ph + uvpitch * ch + (uvpitch * row + col)];
596             if (((row * vsf / blockSize) + (col * hsf / blockSize)) % 2 == 0) {
597               checkVal(row, col, u, "U", 128);
598               checkVal(row, col, v, "V", 128);
599             } else {
600               if (row < halfway) {
601                 checkVal(row, col, u, "U", 85);
602                 checkValMax(row, col, v, "V");
603               } else {
604                 checkVal0(row, col, u, "U");
605                 checkVal(row, col, v, "V", 149);
606               }
607             }
608           }
609         }
610       }
611     } catch (Exception e) {
612       System.out.println("\n" + e.getMessage());
613       retval = 0;
614     }
615
616     if (retval == 0) {
617       for (row = 0; row < ph; row++) {
618         for (col = 0; col < pw; col++) {
619           int y = buf[ypitch * row + col];
620           if (y < 0) y += 256;
621           System.out.format("%3d ", y);
622         }
623         System.out.print("\n");
624       }
625       System.out.print("\n");
626       for (row = 0; row < ch; row++) {
627         for (col = 0; col < cw; col++) {
628           int u = buf[ypitch * ph + (uvpitch * row + col)];
629           if (u < 0) u += 256;
630           System.out.format("%3d ", u);
631         }
632         System.out.print("\n");
633       }
634       System.out.print("\n");
635       for (row = 0; row < ch; row++) {
636         for (col = 0; col < cw; col++) {
637           int v = buf[ypitch * ph + uvpitch * ch + (uvpitch * row + col)];
638           if (v < 0) v += 256;
639           System.out.format("%3d ", v);
640         }
641         System.out.print("\n");
642       }
643     }
644
645     return retval;
646   }
647
648   static void writeJPEG(byte[] jpegBuf, int jpegBufSize, String filename)
649                         throws Exception {
650     File file = new File(filename);
651     FileOutputStream fos = new FileOutputStream(file);
652     fos.write(jpegBuf, 0, jpegBufSize);
653     fos.close();
654   }
655
656   static int compTest(TJCompressor tjc, byte[] dstBuf, int w, int h, int pf,
657                       String baseName) throws Exception {
658     String tempStr;
659     Object srcBuf = null;
660     BufferedImage img = null;
661     String pfStr, pfStrLong;
662     boolean bottomUp = (tjc.get(TJ.PARAM_BOTTOMUP) == 1);
663     int subsamp = tjc.get(TJ.PARAM_SUBSAMP);
664     int jpegQual = tjc.get(TJ.PARAM_QUALITY);
665     int jpegPSV = tjc.get(TJ.PARAM_LOSSLESSPSV);
666     String buStr = bottomUp ? "BU" : "TD";
667     String buStrLong = bottomUp ? "Bottom-Up" : "Top-Down ";
668     int size = 0, ps, imgType = pf;
669
670     if (bi) {
671       pf = biTypePF(imgType);
672       pfStr = biTypeStr(imgType);
673       pfStrLong = pfStr + " (" + PIXFORMATSTR[pf] + ")";
674     } else {
675       pfStr = PIXFORMATSTR[pf];
676       pfStrLong = pfStr;
677     }
678     ps =  TJ.getPixelSize(pf);
679
680     if (bi) {
681       img = new BufferedImage(w, h, imgType);
682       initImg(img, pf, bottomUp);
683       tempStr = baseName + "_enc" + precision + "_" + pfStr + "_" + buStr +
684                 "_" + SUBNAME[subsamp] + "_Q" + jpegQual + ".png";
685       File file = new File(tempStr);
686       ImageIO.write(img, "png", file);
687       tjc.setSourceImage(img, 0, 0, 0, 0);
688     } else {
689       if (precision == 8)
690         srcBuf = new byte[w * h * ps + 1];
691       else
692         srcBuf = new short[w * h * ps + 1];
693       initBuf(srcBuf, w, w * ps, h, pf, bottomUp);
694       if (precision == 8)
695         tjc.setSourceImage((byte[])srcBuf, 0, 0, w, 0, h, pf);
696       else if (precision == 12)
697         tjc.setSourceImage12((short[])srcBuf, 0, 0, w, 0, h, pf);
698       else
699         tjc.setSourceImage16((short[])srcBuf, 0, 0, w, 0, h, pf);
700     }
701     Arrays.fill(dstBuf, (byte)0);
702
703     if (doYUV) {
704       System.out.format("%s %s -> YUV %s ... ", pfStrLong, buStrLong,
705                         SUBNAME_LONG[subsamp]);
706       YUVImage yuvImage = tjc.encodeYUV(yuvAlign);
707       if (checkBufYUV(yuvImage.getBuf(), yuvImage.getSize(), w, h, subsamp,
708                       new TJScalingFactor(1, 1)) == 1)
709         System.out.print("Passed.\n");
710       else {
711         System.out.print("FAILED!\n");
712         exitStatus = -1;
713       }
714
715       System.out.format("YUV %s %s -> JPEG Q%d ... ", SUBNAME_LONG[subsamp],
716                         buStrLong, jpegQual);
717       tjc.setSourceImage(yuvImage);
718     } else {
719       if (lossless)
720         System.out.format("%s %s -> LOSSLESS PSV%d ... ", pfStrLong, buStrLong,
721                           jpegPSV);
722       else
723         System.out.format("%s %s -> %s Q%d ... ", pfStrLong, buStrLong,
724                           SUBNAME_LONG[subsamp], jpegQual);
725     }
726     tjc.compress(dstBuf);
727     size = tjc.getCompressedSize();
728
729     if (lossless)
730       tempStr = baseName + "_enc" + precision + "_" + pfStr + "_" + buStr +
731                 "_LOSSLESS_PSV" + jpegPSV + ".jpg";
732     else
733       tempStr = baseName + "_enc" + precision + "_" + pfStr + "_" + buStr +
734                 "_" + SUBNAME[subsamp] + "_Q" + jpegQual + ".jpg";
735     writeJPEG(dstBuf, size, tempStr);
736     System.out.println("Done.\n  Result in " + tempStr);
737
738     return size;
739   }
740
741   static void decompTest(TJDecompressor tjd, byte[] jpegBuf, int jpegSize,
742                          int w, int h, int pf, String baseName, int subsamp,
743                          TJScalingFactor sf) throws Exception {
744     String pfStr, pfStrLong, tempStr;
745     boolean bottomUp = (tjd.get(TJ.PARAM_BOTTOMUP) == 1);
746     String buStrLong = bottomUp ? "Bottom-Up" : "Top-Down ";
747     int scaledWidth = sf.getScaled(w);
748     int scaledHeight = sf.getScaled(h);
749     int temp1, temp2, imgType = pf;
750     BufferedImage img = null;
751     Object dstBuf = null;
752
753     if (bi) {
754       pf = biTypePF(imgType);
755       pfStr = biTypeStr(imgType);
756       pfStrLong = pfStr + " (" + PIXFORMATSTR[pf] + ")";
757     } else {
758       pfStr = PIXFORMATSTR[pf];
759       pfStrLong = pfStr;
760     }
761
762     tjd.setSourceImage(jpegBuf, jpegSize);
763     tjd.setScalingFactor(sf);
764     if (lossless && subsamp != TJ.SAMP_444 && subsamp != TJ.SAMP_GRAY)
765       subsamp = TJ.SAMP_444;
766     if (tjd.getWidth() != w || tjd.getHeight() != h ||
767         tjd.get(TJ.PARAM_SUBSAMP) != subsamp)
768       throw new Exception("Incorrect JPEG header");
769
770     if (doYUV) {
771       System.out.format("JPEG -> YUV %s ", SUBNAME_LONG[subsamp]);
772       if (!sf.isOne())
773         System.out.format("%d/%d ... ", sf.getNum(), sf.getDenom());
774       else System.out.print("... ");
775       YUVImage yuvImage = tjd.decompressToYUV(yuvAlign);
776       if (checkBufYUV(yuvImage.getBuf(), yuvImage.getSize(), scaledWidth,
777                       scaledHeight, subsamp, sf) == 1)
778         System.out.print("Passed.\n");
779       else {
780         System.out.print("FAILED!\n");  exitStatus = -1;
781       }
782
783       System.out.format("YUV %s -> %s %s ... ", SUBNAME_LONG[subsamp],
784                         pfStrLong, buStrLong);
785       tjd.setSourceImage(yuvImage);
786     } else {
787       System.out.format("JPEG -> %s %s ", pfStrLong, buStrLong);
788       if (!sf.isOne())
789         System.out.format("%d/%d ... ", sf.getNum(), sf.getDenom());
790       else System.out.print("... ");
791     }
792     if (bi)
793       img = tjd.decompress8(imgType);
794     else {
795       if (precision == 8)
796         dstBuf = tjd.decompress8(0, pf);
797       else if (precision == 12)
798         dstBuf = tjd.decompress12(0, pf);
799       else
800         dstBuf = tjd.decompress16(0, pf);
801     }
802
803     if (bi) {
804       tempStr = baseName + "_dec_" + pfStr + "_" + (bottomUp ? "BU" : "TD") +
805                 "_" + SUBNAME[subsamp] + "_" +
806                 (double)sf.getNum() / (double)sf.getDenom() + "x" + ".png";
807       File file = new File(tempStr);
808       ImageIO.write(img, "png", file);
809     }
810
811     if ((bi && checkImg(img, pf, subsamp, sf, bottomUp) == 1) ||
812         (!bi && checkBuf(dstBuf, scaledWidth,
813                          scaledWidth * TJ.getPixelSize(pf), scaledHeight, pf,
814                          subsamp, sf, bottomUp) == 1))
815       System.out.print("Passed.\n");
816     else {
817       System.out.print("FAILED!\n");
818       exitStatus = -1;
819     }
820   }
821
822   static void decompTest(TJDecompressor tjd, byte[] jpegBuf, int jpegSize,
823                          int w, int h, int pf, String baseName, int subsamp)
824                          throws Exception {
825     int i;
826
827     if (lossless) {
828       decompTest(tjd, jpegBuf, jpegSize, w, h, pf, baseName, subsamp,
829                  TJ.UNSCALED);
830       return;
831     }
832
833     TJScalingFactor[] sf = TJ.getScalingFactors();
834     for (i = 0; i < sf.length; i++) {
835       int num = sf[i].getNum();
836       int denom = sf[i].getDenom();
837       if (subsamp == TJ.SAMP_444 || subsamp == TJ.SAMP_GRAY ||
838           ((subsamp == TJ.SAMP_411 || subsamp == TJ.SAMP_441) && num == 1 &&
839            (denom == 2 || denom == 1)) ||
840           (subsamp != TJ.SAMP_411 && subsamp != TJ.SAMP_441 && num == 1 &&
841            (denom == 4 || denom == 2 || denom == 1)))
842         decompTest(tjd, jpegBuf, jpegSize, w, h, pf, baseName, subsamp, sf[i]);
843     }
844   }
845
846   static void doTest(int w, int h, int[] formats, int subsamp, String baseName)
847                      throws Exception {
848     TJCompressor tjc = null;
849     TJDecompressor tjd = null;
850     int size;
851     byte[] dstBuf;
852
853     if (lossless && subsamp != TJ.SAMP_GRAY)
854       subsamp = TJ.SAMP_444;
855
856     dstBuf = new byte[TJ.bufSize(w, h, subsamp)];
857
858     try {
859       tjc = new TJCompressor();
860       tjd = new TJDecompressor();
861
862       if (lossless) {
863         tjc.set(TJ.PARAM_LOSSLESS, 1);
864         tjc.set(TJ.PARAM_LOSSLESSPSV, ((psv++ - 1) % 7) + 1);
865       } else {
866         tjc.set(TJ.PARAM_QUALITY, 100);
867         if (subsamp == TJ.SAMP_422 || subsamp == TJ.SAMP_420 ||
868             subsamp == TJ.SAMP_440 || subsamp == TJ.SAMP_411 ||
869             subsamp == TJ.SAMP_441)
870           tjd.set(TJ.PARAM_FASTUPSAMPLE, 1);
871       }
872       tjc.set(TJ.PARAM_SUBSAMP, subsamp);
873
874       for (int pf : formats) {
875         if (pf < 0) continue;
876         for (int i = 0; i < 2; i++) {
877           tjc.set(TJ.PARAM_BOTTOMUP, i == 1 ? 1 : 0);
878           tjd.set(TJ.PARAM_BOTTOMUP, i == 1 ? 1 : 0);
879           size = compTest(tjc, dstBuf, w, h, pf, baseName);
880           decompTest(tjd, dstBuf, size, w, h, pf, baseName, subsamp);
881           if (pf >= TJ.PF_RGBX && pf <= TJ.PF_XRGB && !bi) {
882             System.out.print("\n");
883             decompTest(tjd, dstBuf, size, w, h, pf + (TJ.PF_RGBA - TJ.PF_RGBX),
884                        baseName, subsamp);
885           }
886           System.out.print("\n");
887         }
888       }
889       System.out.print("--------------------\n\n");
890     } catch (Exception e) {
891       if (tjc != null) tjc.close();
892       if (tjd != null) tjd.close();
893       throw e;
894     }
895     if (tjc != null) tjc.close();
896     if (tjd != null) tjd.close();
897   }
898
899   static void overflowTest() throws Exception {
900     /* Ensure that the various buffer size methods don't overflow */
901     int size = 0;
902     boolean exception = false;
903
904     try {
905       exception = false;
906       size = TJ.bufSize(18919, 18919, TJ.SAMP_444);
907     } catch (Exception e) { exception = true; }
908     if (!exception || size != 0)
909       throw new Exception("TJ.bufSize() overflow");
910     try {
911       exception = false;
912       size = TJ.bufSizeYUV(26755, 1, 26755, TJ.SAMP_444);
913     } catch (Exception e) { exception = true; }
914     if (!exception || size != 0)
915       throw new Exception("TJ.bufSizeYUV() overflow");
916     try {
917       exception = false;
918       size = TJ.bufSizeYUV(26754, 3, 26754, TJ.SAMP_444);
919     } catch (Exception e) { exception = true; }
920     if (!exception || size != 0)
921       throw new Exception("TJ.bufSizeYUV() overflow");
922     try {
923       exception = false;
924       size = TJ.bufSizeYUV(26754, -1, 26754, TJ.SAMP_444);
925     } catch (Exception e) { exception = true; }
926     if (!exception || size != 0)
927       throw new Exception("TJ.bufSizeYUV() overflow");
928     try {
929       exception = false;
930       size = TJ.planeSizeYUV(0, 46341, 0, 46341, TJ.SAMP_444);
931     } catch (Exception e) { exception = true; }
932     if (!exception || size != 0)
933       throw new Exception("TJ.planeSizeYUV() overflow");
934     try {
935       exception = false;
936       size = TJ.planeWidth(0, Integer.MAX_VALUE, TJ.SAMP_420);
937     } catch (Exception e) { exception = true; }
938     if (!exception || size != 0)
939       throw new Exception("TJ.planeWidth() overflow");
940     try {
941       exception = false;
942       size = TJ.planeHeight(0, Integer.MAX_VALUE, TJ.SAMP_420);
943     } catch (Exception e) { exception = true; }
944     if (!exception || size != 0)
945       throw new Exception("TJ.planeHeight() overflow");
946   }
947
948   static void bufSizeTest() throws Exception {
949     int w, h, i, subsamp, numSamp = TJ.NUMSAMP;
950     byte[] srcBuf, dstBuf = null;
951     YUVImage dstImage = null;
952     TJCompressor tjc = null;
953     Random r = new Random();
954
955     try {
956       tjc = new TJCompressor();
957
958       if (lossless) {
959         tjc.set(TJ.PARAM_LOSSLESS, 1);
960         tjc.set(TJ.PARAM_LOSSLESSPSV, ((psv++ - 1) % 7) + 1);
961         numSamp = 1;
962       } else
963         tjc.set(TJ.PARAM_QUALITY, 100);
964
965       System.out.println("Buffer size regression test");
966       for (subsamp = 0; subsamp < numSamp; subsamp++) {
967         tjc.set(TJ.PARAM_SUBSAMP, subsamp);
968         for (w = 1; w < 48; w++) {
969           int maxh = (w == 1) ? 2048 : 48;
970           for (h = 1; h < maxh; h++) {
971             if (h % 100 == 0)
972               System.out.format("%04d x %04d\b\b\b\b\b\b\b\b\b\b\b", w, h);
973             srcBuf = new byte[w * h * 4];
974             if (doYUV)
975               dstImage = new YUVImage(w, yuvAlign, h, subsamp);
976             else
977               dstBuf = new byte[TJ.bufSize(w, h, subsamp)];
978             for (i = 0; i < w * h * 4; i++) {
979               srcBuf[i] = (byte)(r.nextInt(2) * 255);
980             }
981             tjc.setSourceImage(srcBuf, 0, 0, w, 0, h, TJ.PF_BGRX);
982             if (doYUV)
983               tjc.encodeYUV(dstImage);
984             else
985               tjc.compress(dstBuf);
986
987             srcBuf = new byte[h * w * 4];
988             if (doYUV)
989               dstImage = new YUVImage(h, yuvAlign, w, subsamp);
990             else
991               dstBuf = new byte[TJ.bufSize(h, w, subsamp)];
992             for (i = 0; i < h * w * 4; i++) {
993               srcBuf[i] = (byte)(r.nextInt(2) * 255);
994             }
995             tjc.setSourceImage(srcBuf, 0, 0, h, 0, w, TJ.PF_BGRX);
996             if (doYUV)
997               tjc.encodeYUV(dstImage);
998             else
999               tjc.compress(dstBuf);
1000           }
1001           dstImage = null;
1002           dstBuf = null;
1003           System.gc();
1004         }
1005       }
1006       System.out.println("Done.      ");
1007     } catch (Exception e) {
1008       if (tjc != null) tjc.close();
1009       throw e;
1010     }
1011     if (tjc != null) tjc.close();
1012   }
1013
1014   public static void main(String[] argv) {
1015     try {
1016       String testName = "javatest";
1017       for (int i = 0; i < argv.length; i++) {
1018         if (argv[i].equalsIgnoreCase("-yuv"))
1019           doYUV = true;
1020         else if (argv[i].equalsIgnoreCase("-noyuvpad"))
1021           yuvAlign = 1;
1022         else if (argv[i].equalsIgnoreCase("-lossless"))
1023           lossless = true;
1024         else if (argv[i].equalsIgnoreCase("-bi")) {
1025           bi = true;
1026           testName = "javabitest";
1027         } else if (argv[i].equalsIgnoreCase("-precision") &&
1028                    i < argv.length - 1) {
1029           int tempi = -1;
1030
1031           try {
1032             tempi = Integer.parseInt(argv[++i]);
1033           } catch (NumberFormatException e) {}
1034           if (tempi != 8 && tempi != 12 && tempi != 16)
1035             usage();
1036           precision = tempi;
1037           if (precision == 16)
1038             lossless = true;
1039         } else
1040           usage();
1041       }
1042       if (lossless && doYUV)
1043         throw new Exception("Lossless JPEG and YUV encoding/decoding are incompatible.");
1044       if (precision != 8 && doYUV)
1045         throw new Exception("YUV encoding/decoding requires 8-bit data precision.");
1046       if (precision != 8 && bi)
1047         throw new Exception("BufferedImage support requires 8-bit data precision.");
1048
1049       System.out.format("Testing %d-bit precision\n", precision);
1050       sampleSize = (precision == 8 ? 1 : 2);
1051       maxSample = (1 << precision) - 1;
1052       tolerance = (lossless ? 0 : (precision > 8 ? 2 : 1));
1053       redToY = (19595 * maxSample) >> 16;
1054       yellowToY = (58065 * maxSample) >> 16;
1055
1056       if (doYUV)
1057         FORMATS_4SAMPLE[4] = -1;
1058       overflowTest();
1059       doTest(35, 39, bi ? FORMATS_3BYTEBI : FORMATS_3SAMPLE, TJ.SAMP_444,
1060              testName);
1061       doTest(39, 41, bi ? FORMATS_4BYTEBI : FORMATS_4SAMPLE, TJ.SAMP_444,
1062              testName);
1063       doTest(41, 35, bi ? FORMATS_3BYTEBI : FORMATS_3SAMPLE, TJ.SAMP_422,
1064              testName);
1065       if (!lossless) {
1066         doTest(35, 39, bi ? FORMATS_4BYTEBI : FORMATS_4SAMPLE, TJ.SAMP_422,
1067                testName);
1068         doTest(39, 41, bi ? FORMATS_3BYTEBI : FORMATS_3SAMPLE, TJ.SAMP_420,
1069                testName);
1070         doTest(41, 35, bi ? FORMATS_4BYTEBI : FORMATS_4SAMPLE, TJ.SAMP_420,
1071                testName);
1072         doTest(35, 39, bi ? FORMATS_3BYTEBI : FORMATS_3SAMPLE, TJ.SAMP_440,
1073                testName);
1074         doTest(39, 41, bi ? FORMATS_4BYTEBI : FORMATS_4SAMPLE, TJ.SAMP_440,
1075                testName);
1076         doTest(41, 35, bi ? FORMATS_3BYTEBI : FORMATS_3SAMPLE, TJ.SAMP_411,
1077                testName);
1078         doTest(35, 39, bi ? FORMATS_4BYTEBI : FORMATS_4SAMPLE, TJ.SAMP_411,
1079                testName);
1080         doTest(39, 41, bi ? FORMATS_3BYTEBI : FORMATS_3SAMPLE, TJ.SAMP_441,
1081                testName);
1082         doTest(41, 35, bi ? FORMATS_4BYTEBI : FORMATS_4SAMPLE, TJ.SAMP_441,
1083                testName);
1084       }
1085       doTest(39, 41, bi ? FORMATS_GRAYBI : FORMATS_GRAY, TJ.SAMP_GRAY,
1086              testName);
1087       if (!lossless) {
1088         doTest(41, 35, bi ? FORMATS_3BYTEBI : FORMATS_3SAMPLE, TJ.SAMP_GRAY,
1089                testName);
1090         FORMATS_4SAMPLE[4] = -1;
1091         doTest(35, 39, bi ? FORMATS_4BYTEBI : FORMATS_4SAMPLE, TJ.SAMP_GRAY,
1092                testName);
1093       }
1094       if (!bi)
1095         bufSizeTest();
1096       if (doYUV && !bi) {
1097         System.out.print("\n--------------------\n\n");
1098         doTest(48, 48, FORMATS_RGB, TJ.SAMP_444, "javatest_yuv0");
1099         doTest(48, 48, FORMATS_RGB, TJ.SAMP_422, "javatest_yuv0");
1100         doTest(48, 48, FORMATS_RGB, TJ.SAMP_420, "javatest_yuv0");
1101         doTest(48, 48, FORMATS_RGB, TJ.SAMP_440, "javatest_yuv0");
1102         doTest(48, 48, FORMATS_RGB, TJ.SAMP_411, "javatest_yuv0");
1103         doTest(48, 48, FORMATS_RGB, TJ.SAMP_441, "javatest_yuv0");
1104         doTest(48, 48, FORMATS_RGB, TJ.SAMP_GRAY, "javatest_yuv0");
1105         doTest(48, 48, FORMATS_GRAY, TJ.SAMP_GRAY, "javatest_yuv0");
1106       }
1107     } catch (Exception e) {
1108       e.printStackTrace();
1109       exitStatus = -1;
1110     }
1111     System.exit(exitStatus);
1112   }
1113 }