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