1 /*
2  * Copyright (C)2011-2018 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 
TJUnitTest()43   private TJUnitTest() {}
44 
45   static final String CLASS_NAME =
46     new TJUnitTest().getClass().getName();
47 
usage()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/decoding support");
52     System.out.println("-noyuvpad = do not pad each line of each Y, U, and V plane to the nearest");
53     System.out.println("            4-byte boundary");
54     System.out.println("-bi = test BufferedImage support\n");
55     System.exit(1);
56   }
57 
58   static final String[] SUBNAME_LONG = {
59     "4:4:4", "4:2:2", "4:2:0", "GRAY", "4:4:0", "4:1:1"
60   };
61   static final String[] SUBNAME = {
62     "444", "422", "420", "GRAY", "440", "411"
63   };
64 
65   static final String[] PIXFORMATSTR = {
66     "RGB", "BGR", "RGBX", "BGRX", "XBGR", "XRGB", "Grayscale",
67     "RGBA", "BGRA", "ABGR", "ARGB", "CMYK"
68   };
69 
70   static final int[] FORMATS_3BYTE = {
71     TJ.PF_RGB, TJ.PF_BGR
72   };
73   static final int[] FORMATS_3BYTEBI = {
74     BufferedImage.TYPE_3BYTE_BGR
75   };
76   static final int[] FORMATS_4BYTE = {
77     TJ.PF_RGBX, TJ.PF_BGRX, TJ.PF_XBGR, TJ.PF_XRGB, TJ.PF_CMYK
78   };
79   static final int[] FORMATS_4BYTEBI = {
80     BufferedImage.TYPE_INT_BGR, BufferedImage.TYPE_INT_RGB,
81     BufferedImage.TYPE_4BYTE_ABGR, BufferedImage.TYPE_4BYTE_ABGR_PRE,
82     BufferedImage.TYPE_INT_ARGB, BufferedImage.TYPE_INT_ARGB_PRE
83   };
84   static final int[] FORMATS_GRAY = {
85     TJ.PF_GRAY
86   };
87   static final int[] FORMATS_GRAYBI = {
88     BufferedImage.TYPE_BYTE_GRAY
89   };
90   static final int[] FORMATS_RGB = {
91     TJ.PF_RGB
92   };
93 
94   private static boolean doYUV = false;
95   private static int pad = 4;
96   private static boolean bi = false;
97 
98   private static int exitStatus = 0;
99 
biTypePF(int biType)100   static int biTypePF(int biType) {
101     ByteOrder byteOrder = ByteOrder.nativeOrder();
102     switch (biType) {
103     case BufferedImage.TYPE_3BYTE_BGR:
104       return TJ.PF_BGR;
105     case BufferedImage.TYPE_4BYTE_ABGR:
106     case BufferedImage.TYPE_4BYTE_ABGR_PRE:
107       return TJ.PF_ABGR;
108     case BufferedImage.TYPE_BYTE_GRAY:
109       return TJ.PF_GRAY;
110     case BufferedImage.TYPE_INT_BGR:
111       return TJ.PF_RGBX;
112     case BufferedImage.TYPE_INT_RGB:
113       return TJ.PF_BGRX;
114     case BufferedImage.TYPE_INT_ARGB:
115     case BufferedImage.TYPE_INT_ARGB_PRE:
116       return TJ.PF_BGRA;
117     default:
118       return 0;
119     }
120   }
121 
biTypeStr(int biType)122   static String biTypeStr(int biType) {
123     switch (biType) {
124     case BufferedImage.TYPE_3BYTE_BGR:
125       return "3BYTE_BGR";
126     case BufferedImage.TYPE_4BYTE_ABGR:
127       return "4BYTE_ABGR";
128     case BufferedImage.TYPE_4BYTE_ABGR_PRE:
129       return "4BYTE_ABGR_PRE";
130     case BufferedImage.TYPE_BYTE_GRAY:
131       return "BYTE_GRAY";
132     case BufferedImage.TYPE_INT_BGR:
133       return "INT_BGR";
134     case BufferedImage.TYPE_INT_RGB:
135       return "INT_RGB";
136     case BufferedImage.TYPE_INT_ARGB:
137       return "INT_ARGB";
138     case BufferedImage.TYPE_INT_ARGB_PRE:
139       return "INT_ARGB_PRE";
140     default:
141       return "Unknown";
142     }
143   }
144 
initBuf(byte[] buf, int w, int pitch, int h, int pf, int flags)145   static void initBuf(byte[] buf, int w, int pitch, int h, int pf, int flags)
146                       throws Exception {
147     int roffset = TJ.getRedOffset(pf);
148     int goffset = TJ.getGreenOffset(pf);
149     int boffset = TJ.getBlueOffset(pf);
150     int aoffset = TJ.getAlphaOffset(pf);
151     int ps = TJ.getPixelSize(pf);
152     int index, row, col, halfway = 16;
153 
154     if (pf == TJ.PF_GRAY) {
155       Arrays.fill(buf, (byte)0);
156       for (row = 0; row < h; row++) {
157         for (col = 0; col < w; col++) {
158           if ((flags & TJ.FLAG_BOTTOMUP) != 0)
159             index = pitch * (h - row - 1) + col;
160           else
161             index = pitch * row + col;
162           if (((row / 8) + (col / 8)) % 2 == 0)
163             buf[index] = (row < halfway) ? (byte)255 : 0;
164           else
165             buf[index] = (row < halfway) ? 76 : (byte)226;
166         }
167       }
168       return;
169     }
170     if (pf == TJ.PF_CMYK) {
171       Arrays.fill(buf, (byte)255);
172       for (row = 0; row < h; row++) {
173         for (col = 0; col < w; col++) {
174           if ((flags & TJ.FLAG_BOTTOMUP) != 0)
175             index = (h - row - 1) * w + col;
176           else
177             index = row * w + col;
178           if (((row / 8) + (col / 8)) % 2 == 0) {
179             if (row >= halfway) buf[index * ps + 3] = 0;
180           } else {
181             buf[index * ps + 2] = 0;
182             if (row < halfway)
183               buf[index * ps + 1] = 0;
184           }
185         }
186       }
187       return;
188     }
189 
190     Arrays.fill(buf, (byte)0);
191     for (row = 0; row < h; row++) {
192       for (col = 0; col < w; col++) {
193         if ((flags & TJ.FLAG_BOTTOMUP) != 0)
194           index = pitch * (h - row - 1) + col * ps;
195         else
196           index = pitch * row + col * ps;
197         if (((row / 8) + (col / 8)) % 2 == 0) {
198           if (row < halfway) {
199             buf[index + roffset] = (byte)255;
200             buf[index + goffset] = (byte)255;
201             buf[index + boffset] = (byte)255;
202           }
203         } else {
204           buf[index + roffset] = (byte)255;
205           if (row >= halfway)
206             buf[index + goffset] = (byte)255;
207         }
208         if (aoffset >= 0)
209           buf[index + aoffset] = (byte)255;
210       }
211     }
212   }
213 
initIntBuf(int[] buf, int w, int pitch, int h, int pf, int flags)214   static void initIntBuf(int[] buf, int w, int pitch, int h, int pf, int flags)
215                          throws Exception {
216     int rshift = TJ.getRedOffset(pf) * 8;
217     int gshift = TJ.getGreenOffset(pf) * 8;
218     int bshift = TJ.getBlueOffset(pf) * 8;
219     int ashift = TJ.getAlphaOffset(pf) * 8;
220     int index, row, col, halfway = 16;
221 
222     Arrays.fill(buf, 0);
223     for (row = 0; row < h; row++) {
224       for (col = 0; col < w; col++) {
225         if ((flags & TJ.FLAG_BOTTOMUP) != 0)
226           index = pitch * (h - row - 1) + col;
227         else
228           index = pitch * row + col;
229         if (((row / 8) + (col / 8)) % 2 == 0) {
230           if (row < halfway) {
231             buf[index] |= (255 << rshift);
232             buf[index] |= (255 << gshift);
233             buf[index] |= (255 << bshift);
234           }
235         } else {
236           buf[index] |= (255 << rshift);
237           if (row >= halfway)
238             buf[index] |= (255 << gshift);
239         }
240         if (ashift >= 0)
241           buf[index] |= (255 << ashift);
242       }
243     }
244   }
245 
initImg(BufferedImage img, int pf, int flags)246   static void initImg(BufferedImage img, int pf, int flags) throws Exception {
247     WritableRaster wr = img.getRaster();
248     int imgType = img.getType();
249 
250     if (imgType == BufferedImage.TYPE_INT_RGB ||
251         imgType == BufferedImage.TYPE_INT_BGR ||
252         imgType == BufferedImage.TYPE_INT_ARGB ||
253         imgType == BufferedImage.TYPE_INT_ARGB_PRE) {
254       SinglePixelPackedSampleModel sm =
255         (SinglePixelPackedSampleModel)img.getSampleModel();
256       int pitch = sm.getScanlineStride();
257       DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
258       int[] buf = db.getData();
259       initIntBuf(buf, img.getWidth(), pitch, img.getHeight(), pf, flags);
260     } else {
261       ComponentSampleModel sm = (ComponentSampleModel)img.getSampleModel();
262       int pitch = sm.getScanlineStride();
263       DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
264       byte[] buf = db.getData();
265       initBuf(buf, img.getWidth(), pitch, img.getHeight(), pf, flags);
266     }
267   }
268 
checkVal(int row, int col, int v, String vname, int cv)269   static void checkVal(int row, int col, int v, String vname, int cv)
270                        throws Exception {
271     v = (v < 0) ? v + 256 : v;
272     if (v < cv - 1 || v > cv + 1) {
273       throw new Exception("Comp. " + vname + " at " + row + "," + col +
274                           " should be " + cv + ", not " + v);
275     }
276   }
277 
checkVal0(int row, int col, int v, String vname)278   static void checkVal0(int row, int col, int v, String vname)
279                         throws Exception {
280     v = (v < 0) ? v + 256 : v;
281     if (v > 1) {
282       throw new Exception("Comp. " + vname + " at " + row + "," + col +
283                           " should be 0, not " + v);
284     }
285   }
286 
checkVal255(int row, int col, int v, String vname)287   static void checkVal255(int row, int col, int v, String vname)
288                           throws Exception {
289     v = (v < 0) ? v + 256 : v;
290     if (v < 254) {
291       throw new Exception("Comp. " + vname + " at " + row + "," + col +
292                           " should be 255, not " + v);
293     }
294   }
295 
checkBuf(byte[] buf, int w, int pitch, int h, int pf, int subsamp, TJScalingFactor sf, int flags)296   static int checkBuf(byte[] buf, int w, int pitch, int h, int pf, int subsamp,
297                       TJScalingFactor sf, int flags) throws Exception {
298     int roffset = TJ.getRedOffset(pf);
299     int goffset = TJ.getGreenOffset(pf);
300     int boffset = TJ.getBlueOffset(pf);
301     int aoffset = TJ.getAlphaOffset(pf);
302     int ps = TJ.getPixelSize(pf);
303     int index, row, col, retval = 1;
304     int halfway = 16 * sf.getNum() / sf.getDenom();
305     int blockSize = 8 * sf.getNum() / sf.getDenom();
306 
307     try {
308 
309       if (pf == TJ.PF_GRAY)
310         roffset = goffset = boffset = 0;
311 
312       if (pf == TJ.PF_CMYK) {
313         for (row = 0; row < h; row++) {
314           for (col = 0; col < w; col++) {
315             if ((flags & TJ.FLAG_BOTTOMUP) != 0)
316               index = (h - row - 1) * w + col;
317             else
318               index = row * w + col;
319             byte c = buf[index * ps];
320             byte m = buf[index * ps + 1];
321             byte y = buf[index * ps + 2];
322             byte k = buf[index * ps + 3];
323             checkVal255(row, col, c, "C");
324             if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
325               checkVal255(row, col, m, "M");
326               checkVal255(row, col, y, "Y");
327               if (row < halfway)
328                 checkVal255(row, col, k, "K");
329               else
330                 checkVal0(row, col, k, "K");
331             } else {
332               checkVal0(row, col, y, "Y");
333               checkVal255(row, col, k, "K");
334               if (row < halfway)
335                 checkVal0(row, col, m, "M");
336               else
337                 checkVal255(row, col, m, "M");
338             }
339           }
340         }
341         return 1;
342       }
343 
344       for (row = 0; row < halfway; row++) {
345         for (col = 0; col < w; col++) {
346           if ((flags & TJ.FLAG_BOTTOMUP) != 0)
347             index = pitch * (h - row - 1) + col * ps;
348           else
349             index = pitch * row + col * ps;
350           byte r = buf[index + roffset];
351           byte g = buf[index + goffset];
352           byte b = buf[index + boffset];
353           byte a = aoffset >= 0 ? buf[index + aoffset] : (byte)255;
354           if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
355             if (row < halfway) {
356               checkVal255(row, col, r, "R");
357               checkVal255(row, col, g, "G");
358               checkVal255(row, col, b, "B");
359             } else {
360               checkVal0(row, col, r, "R");
361               checkVal0(row, col, g, "G");
362               checkVal0(row, col, b, "B");
363             }
364           } else {
365             if (subsamp == TJ.SAMP_GRAY) {
366               if (row < halfway) {
367                 checkVal(row, col, r, "R", 76);
368                 checkVal(row, col, g, "G", 76);
369                 checkVal(row, col, b, "B", 76);
370               } else {
371                 checkVal(row, col, r, "R", 226);
372                 checkVal(row, col, g, "G", 226);
373                 checkVal(row, col, b, "B", 226);
374               }
375             } else {
376               checkVal255(row, col, r, "R");
377               if (row < halfway) {
378                 checkVal0(row, col, g, "G");
379               } else {
380                 checkVal255(row, col, g, "G");
381               }
382               checkVal0(row, col, b, "B");
383             }
384           }
385           checkVal255(row, col, a, "A");
386         }
387       }
388     } catch (Exception e) {
389       System.out.println("\n" + e.getMessage());
390       retval = 0;
391     }
392 
393     if (retval == 0) {
394       for (row = 0; row < h; row++) {
395         for (col = 0; col < w; col++) {
396           if (pf == TJ.PF_CMYK) {
397             int c = buf[pitch * row + col * ps];
398             int m = buf[pitch * row + col * ps + 1];
399             int y = buf[pitch * row + col * ps + 2];
400             int k = buf[pitch * row + col * ps + 3];
401             if (c < 0) c += 256;
402             if (m < 0) m += 256;
403             if (y < 0) y += 256;
404             if (k < 0) k += 256;
405             System.out.format("%3d/%3d/%3d/%3d ", c, m, y, k);
406           } else {
407             int r = buf[pitch * row + col * ps + roffset];
408             int g = buf[pitch * row + col * ps + goffset];
409             int b = buf[pitch * row + col * ps + boffset];
410             if (r < 0) r += 256;
411             if (g < 0) g += 256;
412             if (b < 0) b += 256;
413             System.out.format("%3d/%3d/%3d ", r, g, b);
414           }
415         }
416         System.out.print("\n");
417       }
418     }
419     return retval;
420   }
421 
checkIntBuf(int[] buf, int w, int pitch, int h, int pf, int subsamp, TJScalingFactor sf, int flags)422   static int checkIntBuf(int[] buf, int w, int pitch, int h, int pf,
423                          int subsamp, TJScalingFactor sf, int flags)
424                          throws Exception {
425     int rshift = TJ.getRedOffset(pf) * 8;
426     int gshift = TJ.getGreenOffset(pf) * 8;
427     int bshift = TJ.getBlueOffset(pf) * 8;
428     int ashift = TJ.getAlphaOffset(pf) * 8;
429     int index, row, col, retval = 1;
430     int halfway = 16 * sf.getNum() / sf.getDenom();
431     int blockSize = 8 * sf.getNum() / sf.getDenom();
432 
433     try {
434       for (row = 0; row < halfway; row++) {
435         for (col = 0; col < w; col++) {
436           if ((flags & TJ.FLAG_BOTTOMUP) != 0)
437             index = pitch * (h - row - 1) + col;
438           else
439             index = pitch * row + col;
440           int r = (buf[index] >> rshift) & 0xFF;
441           int g = (buf[index] >> gshift) & 0xFF;
442           int b = (buf[index] >> bshift) & 0xFF;
443           int a = ashift >= 0 ? (buf[index] >> ashift) & 0xFF : 255;
444           if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
445             if (row < halfway) {
446               checkVal255(row, col, r, "R");
447               checkVal255(row, col, g, "G");
448               checkVal255(row, col, b, "B");
449             } else {
450               checkVal0(row, col, r, "R");
451               checkVal0(row, col, g, "G");
452               checkVal0(row, col, b, "B");
453             }
454           } else {
455             if (subsamp == TJ.SAMP_GRAY) {
456               if (row < halfway) {
457                 checkVal(row, col, r, "R", 76);
458                 checkVal(row, col, g, "G", 76);
459                 checkVal(row, col, b, "B", 76);
460               } else {
461                 checkVal(row, col, r, "R", 226);
462                 checkVal(row, col, g, "G", 226);
463                 checkVal(row, col, b, "B", 226);
464               }
465             } else {
466               checkVal255(row, col, r, "R");
467               if (row < halfway) {
468                 checkVal0(row, col, g, "G");
469               } else {
470                 checkVal255(row, col, g, "G");
471               }
472               checkVal0(row, col, b, "B");
473             }
474           }
475           checkVal255(row, col, a, "A");
476         }
477       }
478     } catch (Exception e) {
479       System.out.println("\n" + e.getMessage());
480       retval = 0;
481     }
482 
483     if (retval == 0) {
484       for (row = 0; row < h; row++) {
485         for (col = 0; col < w; col++) {
486           int r = (buf[pitch * row + col] >> rshift) & 0xFF;
487           int g = (buf[pitch * row + col] >> gshift) & 0xFF;
488           int b = (buf[pitch * row + col] >> bshift) & 0xFF;
489           if (r < 0) r += 256;
490           if (g < 0) g += 256;
491           if (b < 0) b += 256;
492           System.out.format("%3d/%3d/%3d ", r, g, b);
493         }
494         System.out.print("\n");
495       }
496     }
497     return retval;
498   }
499 
checkImg(BufferedImage img, int pf, int subsamp, TJScalingFactor sf, int flags)500   static int checkImg(BufferedImage img, int pf, int subsamp,
501                       TJScalingFactor sf, int flags) throws Exception {
502     WritableRaster wr = img.getRaster();
503     int imgType = img.getType();
504     if (imgType == BufferedImage.TYPE_INT_RGB ||
505         imgType == BufferedImage.TYPE_INT_BGR ||
506         imgType == BufferedImage.TYPE_INT_ARGB ||
507         imgType == BufferedImage.TYPE_INT_ARGB_PRE) {
508       SinglePixelPackedSampleModel sm =
509         (SinglePixelPackedSampleModel)img.getSampleModel();
510       int pitch = sm.getScanlineStride();
511       DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
512       int[] buf = db.getData();
513       return checkIntBuf(buf, img.getWidth(), pitch, img.getHeight(), pf,
514                          subsamp, sf, flags);
515     } else {
516       ComponentSampleModel sm = (ComponentSampleModel)img.getSampleModel();
517       int pitch = sm.getScanlineStride();
518       DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
519       byte[] buf = db.getData();
520       return checkBuf(buf, img.getWidth(), pitch, img.getHeight(), pf, subsamp,
521                       sf, flags);
522     }
523   }
524 
pad(int v, int p)525   static int pad(int v, int p) {
526     return ((v + (p) - 1) & (~((p) - 1)));
527   }
528 
checkBufYUV(byte[] buf, int size, int w, int h, int subsamp, TJScalingFactor sf)529   static int checkBufYUV(byte[] buf, int size, int w, int h, int subsamp,
530                          TJScalingFactor sf) throws Exception {
531     int row, col;
532     int hsf = TJ.getMCUWidth(subsamp) / 8, vsf = TJ.getMCUHeight(subsamp) / 8;
533     int pw = pad(w, hsf), ph = pad(h, vsf);
534     int cw = pw / hsf, ch = ph / vsf;
535     int ypitch = pad(pw, pad), uvpitch = pad(cw, pad);
536     int retval = 1;
537     int correctsize = ypitch * ph +
538                       (subsamp == TJ.SAMP_GRAY ? 0 : uvpitch * ch * 2);
539     int halfway = 16 * sf.getNum() / sf.getDenom();
540     int blockSize = 8 * sf.getNum() / sf.getDenom();
541 
542     try {
543       if (size != correctsize)
544         throw new Exception("Incorrect size " + size + ".  Should be " +
545                             correctsize);
546 
547       for (row = 0; row < ph; row++) {
548         for (col = 0; col < pw; col++) {
549           byte y = buf[ypitch * row + col];
550           if (((row / blockSize) + (col / blockSize)) % 2 == 0) {
551             if (row < halfway)
552               checkVal255(row, col, y, "Y");
553             else
554               checkVal0(row, col, y, "Y");
555           } else {
556             if (row < halfway)
557               checkVal(row, col, y, "Y", 76);
558             else
559               checkVal(row, col, y, "Y", 226);
560           }
561         }
562       }
563       if (subsamp != TJ.SAMP_GRAY) {
564         halfway = 16 / vsf * sf.getNum() / sf.getDenom();
565         for (row = 0; row < ch; row++) {
566           for (col = 0; col < cw; col++) {
567             byte u = buf[ypitch * ph + (uvpitch * row + col)],
568                  v = buf[ypitch * ph + uvpitch * ch + (uvpitch * row + col)];
569             if (((row * vsf / blockSize) + (col * hsf / blockSize)) % 2 == 0) {
570               checkVal(row, col, u, "U", 128);
571               checkVal(row, col, v, "V", 128);
572             } else {
573               if (row < halfway) {
574                 checkVal(row, col, u, "U", 85);
575                 checkVal255(row, col, v, "V");
576               } else {
577                 checkVal0(row, col, u, "U");
578                 checkVal(row, col, v, "V", 149);
579               }
580             }
581           }
582         }
583       }
584     } catch (Exception e) {
585       System.out.println("\n" + e.getMessage());
586       retval = 0;
587     }
588 
589     if (retval == 0) {
590       for (row = 0; row < ph; row++) {
591         for (col = 0; col < pw; col++) {
592           int y = buf[ypitch * row + col];
593           if (y < 0) y += 256;
594           System.out.format("%3d ", y);
595         }
596         System.out.print("\n");
597       }
598       System.out.print("\n");
599       for (row = 0; row < ch; row++) {
600         for (col = 0; col < cw; col++) {
601           int u = buf[ypitch * ph + (uvpitch * row + col)];
602           if (u < 0) u += 256;
603           System.out.format("%3d ", u);
604         }
605         System.out.print("\n");
606       }
607       System.out.print("\n");
608       for (row = 0; row < ch; row++) {
609         for (col = 0; col < cw; col++) {
610           int v = buf[ypitch * ph + uvpitch * ch + (uvpitch * row + col)];
611           if (v < 0) v += 256;
612           System.out.format("%3d ", v);
613         }
614         System.out.print("\n");
615       }
616     }
617 
618     return retval;
619   }
620 
writeJPEG(byte[] jpegBuf, int jpegBufSize, String filename)621   static void writeJPEG(byte[] jpegBuf, int jpegBufSize, String filename)
622                         throws Exception {
623     File file = new File(filename);
624     FileOutputStream fos = new FileOutputStream(file);
625     fos.write(jpegBuf, 0, jpegBufSize);
626     fos.close();
627   }
628 
compTest(TJCompressor tjc, byte[] dstBuf, int w, int h, int pf, String baseName, int subsamp, int jpegQual, int flags)629   static int compTest(TJCompressor tjc, byte[] dstBuf, int w, int h, int pf,
630                       String baseName, int subsamp, int jpegQual, int flags)
631                       throws Exception {
632     String tempStr;
633     byte[] srcBuf = null;
634     BufferedImage img = null;
635     String pfStr, pfStrLong;
636     String buStr = (flags & TJ.FLAG_BOTTOMUP) != 0 ? "BU" : "TD";
637     String buStrLong = (flags & TJ.FLAG_BOTTOMUP) != 0 ?
638                        "Bottom-Up" : "Top-Down ";
639     int size = 0, ps, imgType = pf;
640 
641     if (bi) {
642       pf = biTypePF(imgType);
643       pfStr = biTypeStr(imgType);
644       pfStrLong = pfStr + " (" + PIXFORMATSTR[pf] + ")";
645     } else {
646       pfStr = PIXFORMATSTR[pf];
647       pfStrLong = pfStr;
648     }
649     ps =  TJ.getPixelSize(pf);
650 
651     if (bi) {
652       img = new BufferedImage(w, h, imgType);
653       initImg(img, pf, flags);
654       tempStr = baseName + "_enc_" + pfStr + "_" + buStr + "_" +
655                 SUBNAME[subsamp] + "_Q" + jpegQual + ".png";
656       File file = new File(tempStr);
657       ImageIO.write(img, "png", file);
658       tjc.setSourceImage(img, 0, 0, 0, 0);
659     } else {
660       srcBuf = new byte[w * h * ps + 1];
661       initBuf(srcBuf, w, w * ps, h, pf, flags);
662       tjc.setSourceImage(srcBuf, 0, 0, w, 0, h, pf);
663     }
664     Arrays.fill(dstBuf, (byte)0);
665 
666     tjc.setSubsamp(subsamp);
667     tjc.setJPEGQuality(jpegQual);
668     if (doYUV) {
669       System.out.format("%s %s -> YUV %s ... ", pfStrLong, buStrLong,
670                         SUBNAME_LONG[subsamp]);
671       YUVImage yuvImage = tjc.encodeYUV(pad, flags);
672       if (checkBufYUV(yuvImage.getBuf(), yuvImage.getSize(), w, h, subsamp,
673                       new TJScalingFactor(1, 1)) == 1)
674         System.out.print("Passed.\n");
675       else {
676         System.out.print("FAILED!\n");
677         exitStatus = -1;
678       }
679 
680       System.out.format("YUV %s %s -> JPEG Q%d ... ", SUBNAME_LONG[subsamp],
681                         buStrLong, jpegQual);
682       tjc.setSourceImage(yuvImage);
683     } else {
684       System.out.format("%s %s -> %s Q%d ... ", pfStrLong, buStrLong,
685                         SUBNAME_LONG[subsamp], jpegQual);
686     }
687     tjc.compress(dstBuf, flags);
688     size = tjc.getCompressedSize();
689 
690     tempStr = baseName + "_enc_" + pfStr + "_" + buStr + "_" +
691               SUBNAME[subsamp] + "_Q" + jpegQual + ".jpg";
692     writeJPEG(dstBuf, size, tempStr);
693     System.out.println("Done.\n  Result in " + tempStr);
694 
695     return size;
696   }
697 
decompTest(TJDecompressor tjd, byte[] jpegBuf, int jpegSize, int w, int h, int pf, String baseName, int subsamp, int flags, TJScalingFactor sf)698   static void decompTest(TJDecompressor tjd, byte[] jpegBuf, int jpegSize,
699                          int w, int h, int pf, String baseName, int subsamp,
700                          int flags, TJScalingFactor sf) throws Exception {
701     String pfStr, pfStrLong, tempStr;
702     String buStrLong = (flags & TJ.FLAG_BOTTOMUP) != 0 ?
703                        "Bottom-Up" : "Top-Down ";
704     int scaledWidth = sf.getScaled(w);
705     int scaledHeight = sf.getScaled(h);
706     int temp1, temp2, imgType = pf;
707     BufferedImage img = null;
708     byte[] dstBuf = null;
709 
710     if (bi) {
711       pf = biTypePF(imgType);
712       pfStr = biTypeStr(imgType);
713       pfStrLong = pfStr + " (" + PIXFORMATSTR[pf] + ")";
714     } else {
715       pfStr = PIXFORMATSTR[pf];
716       pfStrLong = pfStr;
717     }
718 
719     tjd.setSourceImage(jpegBuf, jpegSize);
720     if (tjd.getWidth() != w || tjd.getHeight() != h ||
721         tjd.getSubsamp() != subsamp)
722       throw new Exception("Incorrect JPEG header");
723 
724     temp1 = scaledWidth;
725     temp2 = scaledHeight;
726     temp1 = tjd.getScaledWidth(temp1, temp2);
727     temp2 = tjd.getScaledHeight(temp1, temp2);
728     if (temp1 != scaledWidth || temp2 != scaledHeight)
729       throw new Exception("Scaled size mismatch");
730 
731     if (doYUV) {
732       System.out.format("JPEG -> YUV %s ", SUBNAME_LONG[subsamp]);
733       if (!sf.isOne())
734         System.out.format("%d/%d ... ", sf.getNum(), sf.getDenom());
735       else System.out.print("... ");
736       YUVImage yuvImage = tjd.decompressToYUV(scaledWidth, pad, scaledHeight,
737                                               flags);
738       if (checkBufYUV(yuvImage.getBuf(), yuvImage.getSize(), scaledWidth,
739                       scaledHeight, subsamp, sf) == 1)
740         System.out.print("Passed.\n");
741       else {
742         System.out.print("FAILED!\n");  exitStatus = -1;
743       }
744 
745       System.out.format("YUV %s -> %s %s ... ", SUBNAME_LONG[subsamp],
746                         pfStrLong, buStrLong);
747       tjd.setSourceImage(yuvImage);
748     } else {
749       System.out.format("JPEG -> %s %s ", pfStrLong, buStrLong);
750       if (!sf.isOne())
751         System.out.format("%d/%d ... ", sf.getNum(), sf.getDenom());
752       else System.out.print("... ");
753     }
754     if (bi)
755       img = tjd.decompress(scaledWidth, scaledHeight, imgType, flags);
756     else
757       dstBuf = tjd.decompress(scaledWidth, 0, scaledHeight, pf, flags);
758 
759     if (bi) {
760       tempStr = baseName + "_dec_" + pfStr + "_" +
761                 (((flags & TJ.FLAG_BOTTOMUP) != 0) ? "BU" : "TD") + "_" +
762                 SUBNAME[subsamp] + "_" +
763                 (double)sf.getNum() / (double)sf.getDenom() + "x" + ".png";
764       File file = new File(tempStr);
765       ImageIO.write(img, "png", file);
766     }
767 
768     if ((bi && checkImg(img, pf, subsamp, sf, flags) == 1) ||
769         (!bi && checkBuf(dstBuf, scaledWidth,
770                          scaledWidth * TJ.getPixelSize(pf), scaledHeight, pf,
771                          subsamp, sf, flags) == 1))
772       System.out.print("Passed.\n");
773     else {
774       System.out.print("FAILED!\n");
775       exitStatus = -1;
776     }
777   }
778 
decompTest(TJDecompressor tjd, byte[] jpegBuf, int jpegSize, int w, int h, int pf, String baseName, int subsamp, int flags)779   static void decompTest(TJDecompressor tjd, byte[] jpegBuf, int jpegSize,
780                          int w, int h, int pf, String baseName, int subsamp,
781                          int flags) throws Exception {
782     int i;
783     TJScalingFactor[] sf = TJ.getScalingFactors();
784     for (i = 0; i < sf.length; i++) {
785       int num = sf[i].getNum();
786       int denom = sf[i].getDenom();
787       if (subsamp == TJ.SAMP_444 || subsamp == TJ.SAMP_GRAY ||
788           (subsamp == TJ.SAMP_411 && num == 1 &&
789            (denom == 2 || denom == 1)) ||
790           (subsamp != TJ.SAMP_411 && num == 1 &&
791            (denom == 4 || denom == 2 || denom == 1)))
792         decompTest(tjd, jpegBuf, jpegSize, w, h, pf, baseName, subsamp,
793                    flags, sf[i]);
794     }
795   }
796 
doTest(int w, int h, int[] formats, int subsamp, String baseName)797   static void doTest(int w, int h, int[] formats, int subsamp, String baseName)
798                      throws Exception {
799     TJCompressor tjc = null;
800     TJDecompressor tjd = null;
801     int size;
802     byte[] dstBuf;
803 
804     dstBuf = new byte[TJ.bufSize(w, h, subsamp)];
805 
806     try {
807       tjc = new TJCompressor();
808       tjd = new TJDecompressor();
809 
810       for (int pf : formats) {
811         if (pf < 0) continue;
812         for (int i = 0; i < 2; i++) {
813           int flags = 0;
814           if (subsamp == TJ.SAMP_422 || subsamp == TJ.SAMP_420 ||
815               subsamp == TJ.SAMP_440 || subsamp == TJ.SAMP_411)
816             flags |= TJ.FLAG_FASTUPSAMPLE;
817           if (i == 1)
818             flags |= TJ.FLAG_BOTTOMUP;
819           size = compTest(tjc, dstBuf, w, h, pf, baseName, subsamp, 100,
820                           flags);
821           decompTest(tjd, dstBuf, size, w, h, pf, baseName, subsamp, flags);
822           if (pf >= TJ.PF_RGBX && pf <= TJ.PF_XRGB && !bi) {
823             System.out.print("\n");
824             decompTest(tjd, dstBuf, size, w, h, pf + (TJ.PF_RGBA - TJ.PF_RGBX),
825                        baseName, subsamp, flags);
826           }
827           System.out.print("\n");
828         }
829       }
830       System.out.print("--------------------\n\n");
831     } catch (Exception e) {
832       if (tjc != null) tjc.close();
833       if (tjd != null) tjd.close();
834       throw e;
835     }
836     if (tjc != null) tjc.close();
837     if (tjd != null) tjd.close();
838   }
839 
bufSizeTest()840   static void bufSizeTest() throws Exception {
841     int w, h, i, subsamp;
842     byte[] srcBuf, dstBuf = null;
843     YUVImage dstImage = null;
844     TJCompressor tjc = null;
845     Random r = new Random();
846 
847     try {
848       tjc = new TJCompressor();
849       System.out.println("Buffer size regression test");
850       for (subsamp = 0; subsamp < TJ.NUMSAMP; subsamp++) {
851         for (w = 1; w < 48; w++) {
852           int maxh = (w == 1) ? 2048 : 48;
853           for (h = 1; h < maxh; h++) {
854             if (h % 100 == 0)
855               System.out.format("%04d x %04d\b\b\b\b\b\b\b\b\b\b\b", w, h);
856             srcBuf = new byte[w * h * 4];
857             if (doYUV)
858               dstImage = new YUVImage(w, pad, h, subsamp);
859             else
860               dstBuf = new byte[TJ.bufSize(w, h, subsamp)];
861             for (i = 0; i < w * h * 4; i++) {
862               srcBuf[i] = (byte)(r.nextInt(2) * 255);
863             }
864             tjc.setSourceImage(srcBuf, 0, 0, w, 0, h, TJ.PF_BGRX);
865             tjc.setSubsamp(subsamp);
866             tjc.setJPEGQuality(100);
867             if (doYUV)
868               tjc.encodeYUV(dstImage, 0);
869             else
870               tjc.compress(dstBuf, 0);
871 
872             srcBuf = new byte[h * w * 4];
873             if (doYUV)
874               dstImage = new YUVImage(h, pad, w, subsamp);
875             else
876               dstBuf = new byte[TJ.bufSize(h, w, subsamp)];
877             for (i = 0; i < h * w * 4; i++) {
878               srcBuf[i] = (byte)(r.nextInt(2) * 255);
879             }
880             tjc.setSourceImage(srcBuf, 0, 0, h, 0, w, TJ.PF_BGRX);
881             if (doYUV)
882               tjc.encodeYUV(dstImage, 0);
883             else
884               tjc.compress(dstBuf, 0);
885           }
886           dstImage = null;
887           dstBuf = null;
888           System.gc();
889         }
890       }
891       System.out.println("Done.      ");
892     } catch (Exception e) {
893       if (tjc != null) tjc.close();
894       throw e;
895     }
896     if (tjc != null) tjc.close();
897   }
898 
main(String[] argv)899   public static void main(String[] argv) {
900     try {
901       String testName = "javatest";
902       for (int i = 0; i < argv.length; i++) {
903         if (argv[i].equalsIgnoreCase("-yuv"))
904           doYUV = true;
905         else if (argv[i].equalsIgnoreCase("-noyuvpad"))
906           pad = 1;
907         else if (argv[i].equalsIgnoreCase("-bi")) {
908           bi = true;
909           testName = "javabitest";
910         } else
911           usage();
912       }
913       if (doYUV)
914         FORMATS_4BYTE[4] = -1;
915       doTest(35, 39, bi ? FORMATS_3BYTEBI : FORMATS_3BYTE, TJ.SAMP_444,
916              testName);
917       doTest(39, 41, bi ? FORMATS_4BYTEBI : FORMATS_4BYTE, TJ.SAMP_444,
918              testName);
919       doTest(41, 35, bi ? FORMATS_3BYTEBI : FORMATS_3BYTE, TJ.SAMP_422,
920              testName);
921       doTest(35, 39, bi ? FORMATS_4BYTEBI : FORMATS_4BYTE, TJ.SAMP_422,
922              testName);
923       doTest(39, 41, bi ? FORMATS_3BYTEBI : FORMATS_3BYTE, TJ.SAMP_420,
924              testName);
925       doTest(41, 35, bi ? FORMATS_4BYTEBI : FORMATS_4BYTE, TJ.SAMP_420,
926              testName);
927       doTest(35, 39, bi ? FORMATS_3BYTEBI : FORMATS_3BYTE, TJ.SAMP_440,
928              testName);
929       doTest(39, 41, bi ? FORMATS_4BYTEBI : FORMATS_4BYTE, TJ.SAMP_440,
930              testName);
931       doTest(41, 35, bi ? FORMATS_3BYTEBI : FORMATS_3BYTE, TJ.SAMP_411,
932              testName);
933       doTest(35, 39, bi ? FORMATS_4BYTEBI : FORMATS_4BYTE, TJ.SAMP_411,
934              testName);
935       doTest(39, 41, bi ? FORMATS_GRAYBI : FORMATS_GRAY, TJ.SAMP_GRAY,
936              testName);
937       doTest(41, 35, bi ? FORMATS_3BYTEBI : FORMATS_3BYTE, TJ.SAMP_GRAY,
938              testName);
939       FORMATS_4BYTE[4] = -1;
940       doTest(35, 39, bi ? FORMATS_4BYTEBI : FORMATS_4BYTE, TJ.SAMP_GRAY,
941              testName);
942       if (!bi)
943         bufSizeTest();
944       if (doYUV && !bi) {
945         System.out.print("\n--------------------\n\n");
946         doTest(48, 48, FORMATS_RGB, TJ.SAMP_444, "javatest_yuv0");
947         doTest(48, 48, FORMATS_RGB, TJ.SAMP_422, "javatest_yuv0");
948         doTest(48, 48, FORMATS_RGB, TJ.SAMP_420, "javatest_yuv0");
949         doTest(48, 48, FORMATS_RGB, TJ.SAMP_440, "javatest_yuv0");
950         doTest(48, 48, FORMATS_RGB, TJ.SAMP_411, "javatest_yuv0");
951         doTest(48, 48, FORMATS_RGB, TJ.SAMP_GRAY, "javatest_yuv0");
952         doTest(48, 48, FORMATS_GRAY, TJ.SAMP_GRAY, "javatest_yuv0");
953       }
954     } catch (Exception e) {
955       e.printStackTrace();
956       exitStatus = -1;
957     }
958     System.exit(exitStatus);
959   }
960 }
961