View Javadoc

1   // Copyright 2000, CERN, Geneva, Switzerland and University of Santa Cruz, California, U.S.A.
2   package org.freehep.graphicsio.ps;
3   
4   import java.awt.Image;
5   import java.awt.image.ImageProducer;
6   import java.io.DataOutputStream;
7   import java.io.IOException;
8   import java.io.OutputStream;
9   
10  import org.freehep.graphicsio.ImageEncoder;
11  
12  /**
13   * 
14   * @author Charles Loomis
15   * @version $Id: EPSIEncoder.java 8584 2006-08-10 23:06:37Z duns $
16   */
17  public class EPSIEncoder extends ImageEncoder {
18  
19      // Number of bits per byte.
20      final static int maxBitsPerByte = 8;
21  
22      // Number of bytes per scan line (maximum here is 254).
23      final static int maxBytesPerScan = 128;
24  
25      // The number of bits to use to represent the grayscale.
26      private int grayscaleBits;
27  
28      // Boolean which gives the orientation of the image.
29      private boolean portrait;
30  
31      // The width and height of the image.
32      int width, height;
33  
34      // The array to hold the pixels.
35      byte[][] grayPixels;
36  
37      // An array which hold enough bytes for one scan line.
38      Scanline scanline;
39  
40      // Private conversion of bytes to hex digits.
41      private static char[] hexDigit = { '0', '1', '2', '3', '4', '5', '6', '7',
42              '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
43  
44      // Masks to strip off last n bits.
45      final private static byte[] lowBitMask = { (byte) 0, (byte) 1, (byte) 3,
46              (byte) 7, (byte) 15, (byte) 31, (byte) 63, (byte) 127, (byte) 255 };
47  
48      /**
49       * Constructor from Image with number of grayscale bits to use.
50       * 
51       * @param img The image to encode.
52       * @param out The stream to write the GIF to.
53       * @param grayscaleBits Number of grayscale bits to use.
54       * @param portrait Flag indicating a portrait orientation.
55       */
56      public EPSIEncoder(Image img, OutputStream out, int grayscaleBits,
57              boolean portrait) throws IOException {
58  
59          super(img, new DataOutputStream(out));
60          this.grayscaleBits = grayscaleBits;
61          this.portrait = portrait;
62      }
63  
64      /**
65       * Constructor from ImageProducer with number of grayscale bits to use.
66       * 
67       * @param prod The ImageProducer to encode.
68       * @param out The stream to write the GIF to.
69       * @param grayscaleBits Number of grayscale bits to use.
70       * @param portrait Flag indicating a portrait orientation.
71       */
72      public EPSIEncoder(ImageProducer prod, OutputStream out, int grayscaleBits,
73              boolean portrait) throws IOException {
74  
75          super(prod, new DataOutputStream(out));
76          this.grayscaleBits = grayscaleBits;
77          this.portrait = portrait;
78      }
79  
80      protected void encodeStart(int width, int height) throws IOException {
81  
82          this.width = width;
83          this.height = height;
84          grayPixels = new byte[height][width];
85  
86          // Use the appropriate width and height depending on the orientation.
87          int w, h;
88          if (portrait) {
89              w = width;
90              h = height;
91          } else {
92              w = height;
93              h = width;
94          }
95  
96          // Calculate the number of lines in the image.
97          int bitsPerScan = w * grayscaleBits;
98          int bytesPerScan = (bitsPerScan / maxBitsPerByte)
99                  + ((bitsPerScan % maxBitsPerByte == 0) ? 0 : 1);
100         int linesPerScan = (bytesPerScan / maxBytesPerScan)
101                 + ((bytesPerScan % maxBytesPerScan == 0) ? 0 : 1);
102         int lines = linesPerScan * h;
103 
104         // Make a byte array which holds the information for one scan line.
105         scanline = new Scanline(bytesPerScan, grayscaleBits);
106 
107         // Write out the header.
108         putString("%%BeginPreview " + width + " " + height + " "
109                 + grayscaleBits + " " + lines + "\n");
110     }
111 
112     protected void encodePixels(int x, int y, int w, int h, int[] rgbPixels,
113             int off, int scansize) throws IOException {
114 
115         // Save the pixels as a grayscale value.
116         for (int row = 0; row < h; ++row) {
117             for (int column = 0; column < w; column++) {
118                 grayPixels[y + row][column] = toGrayscale(rgbPixels[row
119                         * scansize + off + column]);
120             }
121         }
122     }
123 
124     // Convert a value given as AARRGGBB to a single grayscale value.
125     private byte toGrayscale(int argb) {
126         int mask = 0xFF;
127         double blue = ((double) ((argb >> 0) & mask)) / 255.;
128         double green = ((double) ((argb >> 8) & mask)) / 255.;
129         double red = ((double) ((argb >> 16) & mask)) / 255.;
130 
131         return (byte) (255. * Math.max(0.,
132                 (1. - (0.3 * red + 0.59 * green + 0.11 * blue))));
133     }
134 
135     protected void encodeDone() throws IOException {
136 
137         if (portrait) {
138             for (int row = height - 1; row >= 0; row--) {
139                 scanline.reset();
140                 for (int col = 0; col < width; col++) {
141                     byte gray = grayPixels[row][col];
142                     scanline.add(gray);
143                 }
144                 scanline.put();
145             }
146         } else {
147             for (int col = width - 1; col >= 0; col--) {
148                 scanline.reset();
149                 for (int row = height - 1; row >= 0; row--) {
150                     byte gray = grayPixels[row][col];
151                     scanline.add(gray);
152                 }
153                 scanline.put();
154             }
155         }
156 
157         // Write out the trailer.
158         putString("%%EndPreview\n");
159     }
160 
161     // Write a string to the file.
162     void putString(String s) throws IOException {
163 
164         out.write(s.getBytes());
165     }
166 
167     // Write out a character to the file.
168     void putChar(char c) throws IOException {
169 
170         out.write(c);
171     }
172 
173     // Write out a byte to the GIF file
174     void putByte(byte b) throws IOException {
175 
176         int highNibble = (b >> 4) & 0xF;
177         int lowNibble = b & 0xF;
178 
179         out.write(hexDigit[highNibble]);
180         out.write(hexDigit[lowNibble]);
181     }
182 
183     // This class handles packing the scan data into a hexadecimal byte array.
184     // This only works if the number of bits is 1, 2, 4, or 8.
185     private class Scanline {
186 
187         private byte[] line;
188 
189         private int nbits;
190 
191         private int currentByte;
192 
193         private int currentOffset;
194 
195         public Scanline(int size, int nbits) {
196             line = new byte[size];
197             this.nbits = nbits;
198             reset();
199         }
200 
201         // Reset the scan line. Always zero the last byte in case the
202         // scan line isn't fully used by the image.
203         public void reset() {
204             currentByte = 0;
205             currentOffset = 0;
206             for (int i = 0; i < line.length; i++) {
207                 line[i] = 0;
208             }
209         }
210 
211         public void add(byte b) {
212 
213             // Put the most significant bits in the lowest bits.
214             b >>= (maxBitsPerByte - nbits);
215             b &= lowBitMask[nbits];
216 
217             // OR in the information.
218             line[currentByte] |= b;
219 
220             // Update the offsets.
221             if (maxBitsPerByte - nbits - currentOffset == 0) {
222 
223                 // We're reached the end of a byte, just reset the counters.
224                 currentOffset = 0;
225                 currentByte++;
226             } else {
227 
228                 // Increment the offset and shift the word for the next
229                 // bits of information.
230                 currentOffset += nbits;
231                 line[currentByte] <<= nbits;
232             }
233         }
234 
235         public void put() throws IOException {
236 
237             // Check the last byte. If currentOffset isn't zero then
238             // the last byte hasn't been fully shifted into place.
239             // Do it now!
240             while (maxBitsPerByte - nbits - currentOffset > 0) {
241                 line[line.length - 1] <<= nbits;
242                 currentOffset += nbits;
243             }
244 
245             for (int i = 0; i < line.length; i++) {
246                 if (i % maxBytesPerScan == 0) {
247                     if (i != 0)
248                         putChar('\n');
249                     putChar('%');
250                 }
251                 putByte(line[i]);
252             }
253             putChar('\n');
254         }
255     }
256 
257 }