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