Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/jfor/jfor/tools/jpeg/Encoder.java


1   /**
2    * File: Encoder.java
3    *
4    *
5    * Date         Author                   Changes
6    * Aug 16 01    Andreas Putz             Created
7    * Aug 21 01    Andreas Putz             Bug fixed (bottom and right line)
8    */
9   package org.jfor.jfor.tools.jpeg;
10  
11  
12  import java.awt.Image;
13  
14  import java.awt.Component;
15  import java.awt.MediaTracker;
16  import java.io.BufferedOutputStream;
17  import java.io.OutputStream;
18  import java.io.IOException;
19  
20  import java.io.*;
21  import java.net.URL;
22  import java.awt.Toolkit;
23  
24  /*-----------------------------------------------------------------------------
25   * jfor - Open-Source XSL-FO to RTF converter - see www.jfor.org
26   *
27   * ====================================================================
28   * jfor Apache-Style Software License.
29   * Copyright (c) 2002 by the jfor project. All rights reserved.
30   *
31   * Redistribution and use in source and binary forms, with or without
32   * modification, are permitted provided that the following conditions
33   * are met:
34   *
35   * 1. Redistributions of source code must retain the above copyright
36   * notice, this list of conditions and the following disclaimer.
37   *
38   * 2. Redistributions in binary form must reproduce the above copyright
39   * notice, this list of conditions and the following disclaimer in
40   * the documentation and/or other materials provided with the
41   * distribution.
42   *
43   * 3. The end-user documentation included with the redistribution,
44   * if any, must include the following acknowledgment:
45   * "This product includes software developed
46   * by the jfor project (http://www.jfor.org)."
47   * Alternately, this acknowledgment may appear in the software itself,
48   * if and wherever such third-party acknowledgments normally appear.
49   *
50   * 4. The name "jfor" must not be used to endorse
51   * or promote products derived from this software without prior written
52   * permission.  For written permission, please contact info@jfor.org.
53   *
54   * 5. Products derived from this software may not be called "jfor",
55   * nor may "jfor" appear in their name, without prior written
56   * permission of info@jfor.org.
57   *
58   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
59   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
60   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
61   * DISCLAIMED.  IN NO EVENT SHALL THE JFOR PROJECT OR ITS CONTRIBUTORS BE
62   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
63   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
64   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
65   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
66   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
67   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
68   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
69   * ====================================================================
70   * Contributor(s):
71   * @author Andreas Putz <a.putz@skynamics.com>
72  -----------------------------------------------------------------------------*/
73  
74  /**
75   * Encoder - The JPEG main program which performs a jpeg compression of
76   * an gif or tif image.
77   */
78  public class Encoder extends Component
79  {
80  
81    //////////////////////////////////////////////////
82    // @@ Members
83    //////////////////////////////////////////////////
84  
85    private BufferedOutputStream outStream = null;
86    private JpegInfo jpegObj = null;
87    private Huffman huf = null;
88    private DCT dct = null;
89  
90    private int quality;
91  
92  
93    //////////////////////////////////////////////////
94    // @@ Static definitions
95    //////////////////////////////////////////////////
96  
97    public static int[] jpegNaturalOrder =
98    {
99      0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27,
100     20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
101     58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
102   };
103 
104 
105   //////////////////////////////////////////////////
106   // @@ Construction
107   //////////////////////////////////////////////////
108 
109   /**
110    * Constructor.
111    *
112    * @param quality Percentage to compress
113    * @param out Outputstream
114    */
115   public Encoder (int quality, OutputStream out)
116   {
117     outStream = outStream = new BufferedOutputStream (out);
118     this.quality = quality;
119   }
120 
121 
122   //////////////////////////////////////////////////
123   // @@ Public encode methods
124   //////////////////////////////////////////////////
125 
126   /**
127    * Encodes a JPEG.
128    * @param image AWT Image
129    *
130    * @exception JPEGException On error
131    */
132   public void encodeJPEG (Image image) throws JPEGException
133   {
134     process (image);
135   }
136 
137   /**
138    * Encodes a JPEG.
139    *
140    * @param url URL of a gif or tif image
141    *
142    * @exception JPEGException On error
143    */
144   public void encodeJPEG (URL url) throws JPEGException
145   {
146     Image image = Toolkit.getDefaultToolkit ().getImage (url);
147     process (image);
148   }
149 
150   /**
151    * Encodes a JPEG.
152    *
153    * @param data Byte array with data of a gif or tif image
154    *
155    * @exception JPEGException On error
156    */
157   public void encodeJPEG (byte[] data) throws JPEGException
158   {
159     Image image = Toolkit.getDefaultToolkit ().createImage (data);
160     process (image);
161   }
162 
163 
164   //////////////////////////////////////////////////
165   // @@ Private encode methods
166   //////////////////////////////////////////////////
167 
168   /**
169    * Encode Image process.
170    *
171    * @param image AWT Image
172    *
173    * @exception JPEGException On error
174    */
175   private void process (Image image) throws JPEGException
176   {
177     MediaTracker tracker = new MediaTracker (this);
178     tracker.addImage (image, 0);
179     try
180     {
181       tracker.waitForID (0);
182     }
183     catch (InterruptedException e)
184     {
185       // Got to do something?
186     }
187 
188     /*
189      * Quality of the image.
190      * 0 to 100 and from bad image quality, high compression to good
191      * image quality low compression
192      */
193     setQuality (quality);
194 
195     jpegObj = new JpegInfo (image);
196 
197     huf = new Huffman ();
198 
199     compress ();
200 
201     dispose ();
202   }
203 
204 
205   //////////////////////////////////////////////////
206   // @@ Member access
207   //////////////////////////////////////////////////
208 
209   /**
210    * Gets the quality of jpeg image.
211    *
212    * return A value between 0 and 100
213    */
214   public int getQuality ()
215   {
216     return quality;
217   }
218 
219   /**
220    * Sets the quality of jpeg image.
221    *
222    * @param quality From 0 to 100 %
223    */
224   public void setQuality (int quality)
225   {
226     this.quality = quality;
227     dct = new DCT (quality);
228   }
229 
230 
231   //////////////////////////////////////////////////
232   // @@ Helpers
233   //////////////////////////////////////////////////
234 
235   /**
236    * Dispose.
237    */
238   private void dispose ()
239   {
240     if (jpegObj != null)
241     {
242       jpegObj.dispose ();
243       jpegObj = null;
244     }
245     if (huf != null)
246     {
247       huf.dispose ();
248       huf = null;
249     }
250     if (dct != null)
251     {
252       dct.dispose ();
253       dct = null;
254     }
255   }
256 
257   /**
258    * Compress the image and write to output stream.
259    *
260    * @exception JPEGException On error
261    */
262   private void compress () throws JPEGException
263   {
264     WriteHeaders (outStream);
265     WriteCompressedData (outStream);
266     WriteEOI (outStream);
267 
268     try
269     {
270       outStream.flush ();
271     }
272     catch (IOException e)
273     {
274       throw new JPEGException (e);
275     }
276   }
277 
278   /**
279    * Writes the End Of Image marker.
280    *
281    * @param out Output stream to write out
282    */
283   private void WriteEOI (BufferedOutputStream out)
284   {
285     byte[] EndOfImage =
286     {
287       (byte) 0xFF, (byte) 0xD9
288     };
289 
290     WriteMarker (EndOfImage, out);
291   }
292 
293   /**
294    * Writes the header information.
295    *
296    * @param out Output stream to write out
297    */
298   private void WriteHeaders (BufferedOutputStream out)
299   {
300     int i, j, index, offset, length;
301     int tempArray[];
302 
303     // the SOI marker
304     byte[] startOfImage =
305     {
306       (byte) 0xFF, (byte) 0xD8
307     };
308 
309     WriteMarker (startOfImage, out);
310 
311     // The order of the following headers is quiet inconsequential.
312     // the jFIF header
313     byte jFIF[] = new byte[18];
314 
315     jFIF[0] = (byte) 0xff;
316     jFIF[1] = (byte) 0xe0;
317     jFIF[2] = (byte) 0x00;
318     jFIF[3] = (byte) 0x10;
319     jFIF[4] = (byte) 0x4a;
320     jFIF[5] = (byte) 0x46;
321     jFIF[6] = (byte) 0x49;
322     jFIF[7] = (byte) 0x46;
323     jFIF[8] = (byte) 0x00;
324     jFIF[9] = (byte) 0x01;
325     jFIF[10] = (byte) 0x00;
326     jFIF[11] = (byte) 0x00;
327     jFIF[12] = (byte) 0x00;
328     jFIF[13] = (byte) 0x01;
329     jFIF[14] = (byte) 0x00;
330     jFIF[15] = (byte) 0x01;
331     jFIF[16] = (byte) 0x00;
332     jFIF[17] = (byte) 0x00;
333 
334     WriteArray (jFIF, out);
335 
336     // Comment Header
337     String comment = new String ();
338 
339     comment = jpegObj.getComment ();
340     length = comment.length ();
341 
342     byte cOM[] = new byte[length + 4];
343 
344     cOM[0] = (byte) 0xFF;
345     cOM[1] = (byte) 0xFE;
346     cOM[2] = (byte) ((length >> 8) & 0xFF);
347     cOM[3] = (byte) (length & 0xFF);
348 
349     java.lang.System.arraycopy (jpegObj.getComment().getBytes (), 0, cOM, 4,
350                   jpegObj.getComment().length ());
351     WriteArray (cOM, out);
352 
353     // The dQT header
354     // 0 is the luminance index and 1 is the chrominance index
355     byte dQT[] = new byte[134];
356 
357     dQT[0] = (byte) 0xFF;
358     dQT[1] = (byte) 0xDB;
359     dQT[2] = (byte) 0x00;
360     dQT[3] = (byte) 0x84;
361     offset = 4;
362 
363     for (i = 0; i < 2; i++)
364     {
365       dQT[offset++] = (byte) ((0 << 4) + i);
366       tempArray = dct.getQuantumArray (i);
367 
368       for (j = 0; j < 64; j++)
369       {
370         dQT[offset++] = (byte) tempArray[jpegNaturalOrder[j]];
371       }
372     }
373 
374     WriteArray (dQT, out);
375 
376     // Start of Frame Header
377     byte startOfFrame[] = new byte[19];
378 
379     startOfFrame[0] = (byte) 0xFF;
380     startOfFrame[1] = (byte) 0xC0;
381     startOfFrame[2] = (byte) 0x00;
382     startOfFrame[3] = (byte) 17;
383     startOfFrame[4] = (byte) jpegObj.PRECISION;
384     startOfFrame[5] = (byte) ((jpegObj.getImageHeight () >> 8) & 0xFF);
385     startOfFrame[6] = (byte) ((jpegObj.getImageHeight ()) & 0xFF);
386     startOfFrame[7] = (byte) ((jpegObj.getImageWidth () >> 8) & 0xFF);
387     startOfFrame[8] = (byte) ((jpegObj.getImageWidth ()) & 0xFF);
388     startOfFrame[9] = (byte) jpegObj.getNumberOfComponents ();
389     index = 10;
390 
391     for (i = 0; i < startOfFrame[9]; i++)
392     {
393       startOfFrame[index++] = (byte) jpegObj.COMP_ID[i];
394       startOfFrame[index++] = (byte) ((jpegObj.H_SAMP_FACTOR[i] << 4) + jpegObj.V_SAMP_FACTOR[i]);
395       startOfFrame[index++] = (byte) jpegObj.Q_TABLE_NUMBER[i];
396     }
397 
398     WriteArray (startOfFrame, out);
399 
400     // The dHT Header
401     byte dHT1[], dHT2[], dHT3[], dHT4[];
402     int bytes, temp, oldindex, intermediateindex;
403 
404     length = 2;
405     index = 4;
406     oldindex = 4;
407     dHT1 = new byte[17];
408     dHT4 = new byte[4];
409     dHT4[0] = (byte) 0xFF;
410     dHT4[1] = (byte) 0xC4;
411 
412     for (i = 0; i < 4; i++)
413     {
414       bytes = 0;
415       dHT1[index++ - oldindex] = (byte) huf.getBits (i)[0];
416 
417       for (j = 1; j < 17; j++)
418       {
419         temp = huf.getBits (i)[j];
420         dHT1[index++ - oldindex] = (byte) temp;
421         bytes += temp;
422       }
423 
424       intermediateindex = index;
425       dHT2 = new byte[bytes];
426 
427       for (j = 0; j < bytes; j++)
428       {
429         dHT2[index++ - intermediateindex] = (byte) huf.getVal (i)[j];
430       }
431 
432       dHT3 = new byte[index];
433 
434       java.lang.System.arraycopy (dHT4, 0, dHT3, 0, oldindex);
435       java.lang.System.arraycopy (dHT1, 0, dHT3, oldindex, 17);
436       java.lang.System.arraycopy (dHT2, 0, dHT3, oldindex + 17, bytes);
437 
438       dHT4 = dHT3;
439       oldindex = index;
440     }
441 
442     dHT4[2] = (byte) (((index - 2) >> 8) & 0xFF);
443     dHT4[3] = (byte) ((index - 2) & 0xFF);
444 
445     WriteArray (dHT4, out);
446 
447 
448     // Start of Scan Header
449     byte SOS[] = new byte[14];
450 
451     SOS[0] = (byte) 0xFF;
452     SOS[1] = (byte) 0xDA;
453     SOS[2] = (byte) 0x00;
454     SOS[3] = (byte) 12;
455     SOS[4] = (byte) jpegObj.getNumberOfComponents ();
456     index = 5;
457 
458     for (i = 0; i < SOS[4]; i++)
459     {
460       SOS[index++] = (byte) jpegObj.COMP_ID[i];
461       SOS[index++] = (byte) ((jpegObj.D_C_TABLE_NUMBER[i] << 4) + jpegObj.A_C_TABLE_NUMBER[i]);
462     }
463 
464     SOS[index++] = (byte) jpegObj.ss;
465     SOS[index++] = (byte) jpegObj.se;
466     SOS[index++] = (byte) ((jpegObj.ah << 4) + jpegObj.al);
467 
468     WriteArray (SOS, out);
469 
470   }
471 
472   /**
473    * Writes the compressed data to the outputstream
474    *
475    * @param outputStream Stream to write out the jpeg data
476    */
477   private void WriteCompressedData (BufferedOutputStream outputStream)
478   {
479     float dctArray1[][] = new float[8][8];
480     double dctArray2[][] = new double[8][8];
481     int dctArray3[] = new int[8 * 8];
482 
483     /*
484      * This method controls the compression of the image.
485      * Starting at the upper left of the image, it compresses 8x8 blocks
486      * of data until the entire image has been compressed.
487      */
488 
489     int lastDCvalue[] = new int [jpegObj.getNumberOfComponents ()];
490     int zeroArray[] = new int[64];    // initialized to hold all zeros
491 
492     int nothing = 0, not;
493     int minBlockWidth, minBlockHeight;
494 
495     // This initial setting of minBlockWidth and minBlockHeight is done to
496     // ensure they start with values larger than will actually be the case.
497     minBlockWidth = ((jpegObj.getImageWidth () % 8 != 0)
498              ? (int) (Math.floor ((double) jpegObj.getImageWidth () / 8.0) + 1) * 8 : jpegObj.getImageWidth ());
499     minBlockHeight = ((jpegObj.getImageHeight () % 8 != 0)
500               ? (int) (Math.floor ((double) jpegObj.getImageHeight () / 8.0) + 1) * 8 : jpegObj.getImageHeight ());
501 
502     for (int comp = 0; comp < jpegObj.getNumberOfComponents (); comp++)
503     {
504       minBlockWidth = Math.min (minBlockWidth, jpegObj.getBlockWidth (comp));
505       minBlockHeight = Math.min (minBlockHeight, jpegObj.getBlockHeight (comp));
506     }
507 
508     for (int r = 0; r < minBlockHeight; r++)
509     {
510       for (int c = 0; c < minBlockWidth; c++)
511       {
512         int xpos = c * 8;
513         int ypos = r * 8;
514 
515         for (int comp = 0; comp < jpegObj.getNumberOfComponents (); comp++)
516         {
517       int blockWidth = jpegObj.getBlockWidth (comp);
518       int blockHeight = jpegObj.getBlockHeight (comp);
519           float[][] inputArray = (float[][]) jpegObj.getComponent (comp);
520 
521           for (int i = 0; i < jpegObj.V_SAMP_FACTOR[comp]; i++)
522           {
523             for (int j = 0; j < jpegObj.H_SAMP_FACTOR[comp]; j++)
524             {
525               int xblockoffset = j * 8;
526               int yblockoffset = i * 8;
527 
528               for (int a = 0; a < 8; a++)
529               {
530                 for (int b = 0; b < 8; b++)
531                 {
532 
533                   // I believe this is where the dirty line at the bottom of the image is
534                   // coming from.  I need to do a check here to make sure I'm not reading past
535                   // image data.
536                   // This seems to not be a big issue right now. (04/04/98)
537 
538                   dctArray1[a][b] =
539                     inputArray[ypos + yblockoffset + a][xpos + xblockoffset + b];
540                 }
541               }
542 
543               // The following code commented out because on some images this technique
544               // results in poor right and bottom borders.
545                if ((! jpegObj.isLastColumnDummy (comp) || c < blockWidth - 1)
546                  && (!jpegObj.isLastRowDummy (comp) || r < blockHeight - 1))
547                {
548                     dctArray2 = dct.forwardDCT (dctArray1);
549                     dctArray3 = dct.quantizeBlock (dctArray2, jpegObj.Q_TABLE_NUMBER[comp]);
550 
551                }
552                else
553                {
554                  zeroArray[0] = dctArray3[0];
555                  zeroArray[0] = lastDCvalue[comp];
556                  dctArray3 = zeroArray;
557                }
558 
559               huf.encodeHuffmanBlock (outputStream, dctArray3, lastDCvalue[comp],
560                            jpegObj.D_C_TABLE_NUMBER[comp],
561                            jpegObj.A_C_TABLE_NUMBER[comp]);
562 
563               lastDCvalue[comp] = dctArray3[0];
564             }
565           }
566         }
567       }
568     }
569 
570     huf.flushBuffer (outputStream);
571   }
572 
573   /**
574    * Writes a marker.
575    *
576    * @param data Data to write
577    * @param out Output stream to write out
578    */
579   private void WriteMarker (byte[] data, BufferedOutputStream out)
580   {
581     try
582     {
583       out.write (data, 0, 2);
584     }
585     catch (IOException e)
586     {
587       System.err.println ("Error: " + e.getMessage ());
588     }
589   }
590 
591   /**
592    * Writes a array.
593    *
594    * @param data Data to write
595    * @param out Output stream to write out
596    */
597   private void WriteArray (byte[] data, BufferedOutputStream out)
598   {
599     int i, length;
600 
601     try
602     {
603       length = (((int) (data[2] & 0xFF)) << 8) + (int) (data[3] & 0xFF) + 2;
604 
605       out.write (data, 0, length);
606     }
607     catch (IOException e)
608     {
609       System.out.println ("IO Error: " + e.getMessage ());
610     }
611   }
612 
613 
614   //////////////////////////////////////////////////
615   // @@ Main method
616   //////////////////////////////////////////////////
617 
618   /**
619    * Main method
620    *
621    * @param args Command line arguments
622    */
623   public static void main (String args[])
624   {
625     String string = new String ();
626     int defaultQuality = 80;
627 
628     // Check to see if the input file name has one of the extensions:
629     // .tif, .gif, .jpg
630     // If not, print the standard use info.
631     if (args.length < 2)
632     {
633       StandardUsage ();
634     }
635 
636     if (!args[0].endsWith (".jpg") &&!args[0].endsWith (".tif") &&!args[0].endsWith (".gif"))
637     {
638       StandardUsage ();
639 
640       // First check to see if there is an OutputFile argument.  If there isn't
641       // then name the file "InputFile".jpg
642       // Second check to see if the .jpg extension is on the OutputFile argument.
643       // If there isn't one, add it.
644       // Need to check for the existence of the output file.  If it exists already,
645       // rename the file with a # after the file name, then the .jpg extension.
646     }
647 
648     if (args.length < 3)
649     {
650       string = args[0].substring (0, args[0].lastIndexOf (".")) + ".jpg";
651     }
652     else
653     {
654       string = args[2];
655 
656       if (string.endsWith (".tif") || string.endsWith (".gif"))
657       {
658         string = string.substring (0, string.lastIndexOf ("."));
659       }
660 
661       if (!string.endsWith (".jpg"))
662       {
663         string = string.concat (".jpg");
664       }
665     }
666 
667     File outFile = new File (string);
668 
669     for (int i = 0; outFile.exists (); i++)
670     {
671       outFile = new File (string.substring (0, string.lastIndexOf (".")) + i + ".jpg");
672 
673       if (i > 100)
674       {
675         System.exit (0);
676       }
677     }
678 
679     File inFile = new File (args[0]);
680 
681     try
682     {
683       if (inFile.exists ())
684       {
685         int qual = defaultQuality;
686         FileOutputStream dataOut = null;
687 
688         try
689         {
690           dataOut = new FileOutputStream (outFile);
691         }
692         catch (IOException e)
693         {
694         }
695 
696         try
697         {
698           qual = Integer.parseInt (args[1]);
699         }
700         catch (NumberFormatException e)
701         {
702           StandardUsage ();
703         }
704 
705         Image image = Toolkit.getDefaultToolkit ().getImage (args[0]);
706         Encoder jpgEncoder = new Encoder (qual, dataOut);
707         jpgEncoder.encodeJPEG (image);
708 
709         try
710         {
711           dataOut.close ();
712         }
713         catch (IOException e)
714         {
715         }
716       }
717       else
718       {
719         System.out.println ("I couldn't find " + args[0] + ". Is it in another directory?");
720       }
721     }
722     catch (JPEGException e)
723     {
724       e.printStackTrace ();
725     }
726     finally
727     {
728       System.exit (0);
729     }
730   }
731 
732   /**
733    * Standard usage method.
734    */
735   public static void StandardUsage()
736   {
737     System.out.println("Program usage: java Jpeg \"InputImage\".\"ext\" Quality [\"OutputFile\"[.jpg]]");
738     System.exit(0);
739   }
740 }
741