001    /*
002     * PalmCodec
003     *
004     * Copyright (c) 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.codecs;
009    
010    import java.io.DataInput;
011    import java.io.DataOutput;
012    import java.io.IOException;
013    import java.io.RandomAccessFile;
014    import net.sourceforge.jiu.codecs.ImageCodec;
015    import net.sourceforge.jiu.codecs.InvalidFileStructureException;
016    import net.sourceforge.jiu.codecs.UnsupportedTypeException;
017    import net.sourceforge.jiu.codecs.WrongFileFormatException;
018    import net.sourceforge.jiu.data.BilevelImage;
019    import net.sourceforge.jiu.data.ByteChannelImage;
020    import net.sourceforge.jiu.data.Gray8Image;
021    import net.sourceforge.jiu.data.MemoryBilevelImage;
022    import net.sourceforge.jiu.data.MemoryGray8Image;
023    import net.sourceforge.jiu.data.MemoryPaletted8Image;
024    import net.sourceforge.jiu.data.MemoryRGB24Image;
025    import net.sourceforge.jiu.data.PixelImage;
026    import net.sourceforge.jiu.data.Palette;
027    import net.sourceforge.jiu.data.Paletted8Image;
028    import net.sourceforge.jiu.data.RGBIndex;
029    import net.sourceforge.jiu.data.RGB24Image;
030    import net.sourceforge.jiu.ops.MissingParameterException;
031    import net.sourceforge.jiu.ops.OperationFailedException;
032    import net.sourceforge.jiu.ops.WrongParameterException;
033    import net.sourceforge.jiu.util.ArrayConverter;
034    import net.sourceforge.jiu.util.SeekableByteArrayOutputStream;
035    
036    /**
037     * A codec to read and write image files in the native image file format of
038     * <a target="_top" href="http://www.palmos.com/">Palm OS</a>,
039     * an operating system for handheld devices.
040     *
041     * <h3>Supported file types when loading</h3>
042     * This codec reads uncompressed, scan line compressed and RLE compressed Palm files
043     * with bit depths of 1, 2, 4, 8 and 16 bits per pixel.
044     * Not supported are the Packbits compression algorithm or any color depths other
045     * then the aforementioned.
046     *
047     * <h3>Supported image types when saving</h3>
048     * Compression types <em>Uncompressed</em>, <em>Scan line</em> and <em>RLE</em> are written.
049     * When saving an image as a Palm, the image data classes will be mapped to file types as follows:
050     * <ul>
051     * <li>{@link BilevelImage}: will be saved as a 1 bit per pixel, monochrome file.</li>
052     * <li>{@link Gray8Image}: will be saved as an 8 bits per pixel file with a custom
053     *  palette which will contain the 256 shades of gray from black - (0, 0, 0) - to
054     *  white - (255, 255, 255).</li>
055     * <li>{@link Paletted8Image}: it is first checked if the image is using the
056     *  Palm system 8 bits per pixel palette. If so, an 8 bits per pixel file
057     *  with no custom palette is written, otherwise an 8 bits per pixel file
058     *  with a custom palette (of the original length) is written.
059     * </li>
060     * <li>{@link RGB24Image}: will be saved as a 16 bits per pixel, direct color file.
061     *   Some information will get lost when converting from 24 to 16 bits per pixel.
062     *   Instead of 256 shades for each red, green and blue (and thus, 256<sup>3</sup> = 16,777,216 
063     *   possible colors) the resulting file will only
064     *   use 32 shades of red and blue and 64 shades of green (65,536 possible colors).</li>
065     * </ul>
066     *
067     * <h3>I/O objects</h3>
068     * This codec supports all the I/O classes that are considered in ImageCodec.
069     * If you save images and want a correct <em>compressed size</em> field
070     * in the resulting Palm file, make sure to give a RandomAccessFile object to
071     * the codec.
072     * Or simply use {@link #setFile} which does that automatically.
073     *
074     * <h3>File extension</h3>
075     * This codec suggests <code>.palm</code> as file extension for this file format.
076     * This is by no means official, but I find it helpful.
077     *
078     * <h3>Transparency information</h3>
079     * The transparency index in a Palm file is saved and loaded, but a loaded index
080     * is not stored in the image object as there is no support for transparency information of 
081     * any kind in PixelImage yet.
082     * The RGB transparency color that is present in a file only in direct color mode
083     * is read but not written.
084     *
085     * <h3>Bounds</h3>
086     * The bounds concept of ImageCodec is supported so that you can load or save 
087     * only part of an image.
088     *
089     * <h3>Open questions on the Palm file format</h3>
090     * <ul>
091     * <li>How does Packbits compression work? Where can I get sample files or a Windows 
092     *  converter that writes those?</li>
093     * <li>How is FLAG_4_BYTE_FIELDS interpreted? When are four byte fields used?</li>
094     * <li>When loading a 4 bpp Palm image file without a custom palette,
095     *  how is the decoder supposed to know whether to take the predefined 
096     *  color or grayscale palette with 16 entries?</li>
097     * </ul>
098     * If you can answer any of these, please <a target="_top" href="http://www.geocities.com/marcoschmidt.geo/contact.html">contact me</a>!
099     *
100     * <h3>Known problems</h3>
101     * <ul>
102     * <li>Unfortunately, the Palm image file format does not include a signature that
103     *  makes it easy to identify such a file. Various checks on allowed combinations of
104     *  color depth, compression type etc. will prevent the codec from trying to interpret
105     *  all files as Palm image files, but there is still a probability of false 
106     *  identification.</li>
107     * </ul>
108     *
109     * <h3>Usage examples</h3>
110     * Load an image from a Palm image file:
111     * <pre>
112     * PalmCodec codec = new PalmCodec();
113     * codec.setFile("test.palm", CodecMode.LOAD);
114     * codec.process();
115     * PixelImage image = codec.getImage();
116     * codec.close();
117     * </pre>
118     * Save an image to a Palm file using RLE compression:
119     * <pre>
120     * PalmCodec codec = new PalmCodec();
121     * codec.setImage(image);
122     * codec.setCompression(PalmCodec.COMPRESSION_RLE);
123     * codec.setFile("out.palm", CodecMode.SAVE);
124     * codec.process();
125     * codec.close();
126     * </pre>
127     *
128     * <h3>Background</h3>
129     * The code is based on:
130     * <ul>
131     * <li>the specification 
132     * <a target="_top" href="http://www.kawt.de/doc/palmimage.html">Palm
133     * Native Image Format</a>,</li>
134     * <li>the source code of the utilities <code>pnmtopalm</code> and
135     * <code>palmtopnm</code> that are part of the
136     * <a href="http://netpbm.sourceforge.net" target="_top">Netpbm</a> package,</li>
137     * <li><a href="http://oasis.palm.com/dev/kb/papers/1831.cfm" target="_top">Palm OS Compressed Bitmaps</a> by Ken Krugler,
138     *  a Palm Developer Knowledge Base article on the scan line compression algorithm and</li>
139     * <li><a href="http://oasis.palm.com/dev/kb/manuals/sdk/Bitmap.cfm" target="_top">Palm OS Bitmaps</a>,
140     *  also part of the Palm Developer Knowledge Base, contains general information on the 
141     *  structure of Palm images.</li>
142     * </ul>
143     * I also received helpful feedback and test images from Bill Janssen.
144     *
145     * @author Marco Schmidt
146     */
147    public class PalmCodec extends ImageCodec
148    {
149            /**
150             * Constant for compression type <em>Uncompressed</em>.
151             */
152            public static final int COMPRESSION_NONE = 255;
153    
154            /**
155             * Constant for compression type <em>Packbits</em>.
156             */
157            public static final int COMPRESSION_PACKBITS = 2;
158    
159            /**
160             * Constant for compression type <em>RLE (run length encoding)</em>.
161             */
162            public static final int COMPRESSION_RLE = 1;
163    
164            /**
165             * Constant for compression type <em>Scanline</em>.
166             */
167            public static final int COMPRESSION_SCANLINE = 0;
168    
169            private static final int FLAG_COMPRESSED = 0x8000;
170            private static final int FLAG_COLOR_TABLE = 0x4000;
171            private static final int FLAG_TRANSPARENCY = 0x2000;
172            private static final int FLAG_INDIRECT = 0x1000;
173            private static final int FLAG_FOR_SCREEN = 0x0800;
174            private static final int FLAG_DIRECT_COLOR = 0x0400;
175            private static final int FLAG_4_BYTE_FIELD = 0x0200;
176    
177            // following the Palm OS default palettes
178            // instead of short we could use byte but that would require converting
179            // all values > 127 to byte representation (-128 .. 128)
180    
181            private static final short[][] PALM_SYSTEM_PALETTE_4_GRAY = new short[][]
182            {
183                    { 255, 255, 255}, { 192, 192, 192}, { 128, 128, 128 }, {   0,   0,   0 }
184            };
185    
186            private static final short[][] PALM_SYSTEM_PALETTE_16_COLOR = new short[][]
187            {
188                    { 255, 255, 255}, { 128, 128, 128 }, { 128,   0,   0 }, { 128, 128,   0 },
189                    {   0, 128,   0}, {   0, 128, 128 }, {   0,   0, 128 }, { 128,   0, 128 },
190                    { 255,   0, 255}, { 192, 192, 192 }, { 255,   0,   0 }, { 255, 255,   0 },
191                    {   0, 255,   0}, {   0, 255, 255 }, {   0,   0, 255 }, {   0,   0,   0 }
192            };
193    
194            private static final short[][] PALM_SYSTEM_PALETTE_16_GRAY = new short[][]
195            {
196                    { 255, 255, 255}, { 238, 238, 238 }, { 221, 221, 221 }, { 204, 204, 204 },
197                    { 187, 187, 187}, { 170, 170, 170 }, { 153, 153, 153 }, { 136, 136, 136 },
198                    { 119, 119, 119}, { 102, 102, 102 }, {  85,  85,  85 }, {  68,  68,  68 },
199                    {  51,  51,  51}, {  34,  34,  34 }, {  17,  17,  17 }, {   0,   0,   0 }
200            };
201    
202            private static final short[][] PALM_SYSTEM_PALETTE_256 = new short[][]
203            {
204                    { 255, 255, 255 }, { 255, 204, 255 }, { 255, 153, 255 }, { 255, 102, 255 }, 
205                    { 255,  51, 255 }, { 255,   0, 255 }, { 255, 255, 204 }, { 255, 204, 204 }, 
206                    { 255, 153, 204 }, { 255, 102, 204 }, { 255,  51, 204 }, { 255,   0, 204 }, 
207                    { 255, 255, 153 }, { 255, 204, 153 }, { 255, 153, 153 }, { 255, 102, 153 }, 
208                    { 255,  51, 153 }, { 255,   0, 153 }, { 204, 255, 255 }, { 204, 204, 255 },
209                    { 204, 153, 255 }, { 204, 102, 255 }, { 204,  51, 255 }, { 204,   0, 255 },
210                    { 204, 255, 204 }, { 204, 204, 204 }, { 204, 153, 204 }, { 204, 102, 204 },
211                    { 204,  51, 204 }, { 204,   0, 204 }, { 204, 255, 153 }, { 204, 204, 153 },
212                    { 204, 153, 153 }, { 204, 102, 153 }, { 204,  51, 153 }, { 204,   0, 153 },
213                    { 153, 255, 255 }, { 153, 204, 255 }, { 153, 153, 255 }, { 153, 102, 255 },
214                    { 153,  51, 255 }, { 153,   0, 255 }, { 153, 255, 204 }, { 153, 204, 204 },
215                    { 153, 153, 204 }, { 153, 102, 204 }, { 153,  51, 204 }, { 153,   0, 204 },
216                    { 153, 255, 153 }, { 153, 204, 153 }, { 153, 153, 153 }, { 153, 102, 153 },
217                    { 153,  51, 153 }, { 153,   0, 153 }, { 102, 255, 255 }, { 102, 204, 255 },
218                    { 102, 153, 255 }, { 102, 102, 255 }, { 102,  51, 255 }, { 102,   0, 255 },
219                    { 102, 255, 204 }, { 102, 204, 204 }, { 102, 153, 204 }, { 102, 102, 204 },
220                    { 102,  51, 204 }, { 102,   0, 204 }, { 102, 255, 153 }, { 102, 204, 153 },
221                    { 102, 153, 153 }, { 102, 102, 153 }, { 102,  51, 153 }, { 102,   0, 153 },
222                    {  51, 255, 255 }, {  51, 204, 255 }, {  51, 153, 255 }, {  51, 102, 255 },
223                    {  51,  51, 255 }, {  51,   0, 255 }, {  51, 255, 204 }, {  51, 204, 204 },
224                    {  51, 153, 204 }, {  51, 102, 204 }, {  51,  51, 204 }, {  51,   0, 204 },
225                    {  51, 255, 153 }, {  51, 204, 153 }, {  51, 153, 153 }, {  51, 102, 153 },
226                    {  51,  51, 153 }, {  51,   0, 153 }, {   0, 255, 255 }, {   0, 204, 255 },
227                    {   0, 153, 255 }, {   0, 102, 255 }, {   0,  51, 255 }, {   0,   0, 255 },
228                    {   0, 255, 204 }, {   0, 204, 204 }, {   0, 153, 204 }, {   0, 102, 204 },
229                    {   0,  51, 204 }, {   0,   0, 204 }, {   0, 255, 153 }, {   0, 204, 153 },
230                    {   0, 153, 153 }, {   0, 102, 153 }, {   0,  51, 153 }, {   0,   0, 153 },
231                    { 255, 255, 102 }, { 255, 204, 102 }, { 255, 153, 102 }, { 255, 102, 102 },
232                    { 255,  51, 102 }, { 255,   0, 102 }, { 255, 255,  51 }, { 255, 204,  51 },
233                    { 255, 153,  51 }, { 255, 102,  51 }, { 255,  51,  51 }, { 255,   0,  51 },
234                    { 255, 255,   0 }, { 255, 204,   0 }, { 255, 153,   0 }, { 255, 102,   0 },
235                    { 255,  51,   0 }, { 255,   0,   0 }, { 204, 255, 102 }, { 204, 204, 102 },
236                    { 204, 153, 102 }, { 204, 102, 102 }, { 204,  51, 102 }, { 204,   0, 102 },
237                    { 204, 255,  51 }, { 204, 204,  51 }, { 204, 153,  51 }, { 204, 102,  51 },
238                    { 204,  51,  51 }, { 204,   0,  51 }, { 204, 255,   0 }, { 204, 204,   0 },
239                    { 204, 153,   0 }, { 204, 102,   0 }, { 204,  51,   0 }, { 204,   0,   0 },
240                    { 153, 255, 102 }, { 153, 204, 102 }, { 153, 153, 102 }, { 153, 102, 102 },
241                    { 153,  51, 102 }, { 153,   0, 102 }, { 153, 255,  51 }, { 153, 204,  51 },
242                    { 153, 153,  51 }, { 153, 102,  51 }, { 153,  51,  51 }, { 153,   0,  51 },
243                    { 153, 255,   0 }, { 153, 204,   0 }, { 153, 153,   0 }, { 153, 102,   0 },
244                    { 153,  51,   0 }, { 153,   0,   0 }, { 102, 255, 102 }, { 102, 204, 102 },
245                    { 102, 153, 102 }, { 102, 102, 102 }, { 102,  51, 102 }, { 102,   0, 102 },
246                    { 102, 255,  51 }, { 102, 204,  51 }, { 102, 153,  51 }, { 102, 102,  51 },
247                    { 102,  51,  51 }, { 102,   0,  51 }, { 102, 255,   0 }, { 102, 204,   0 },
248                    { 102, 153,   0 }, { 102, 102,   0 }, { 102,  51,   0 }, { 102,   0,   0 },
249                    {  51, 255, 102 }, {  51, 204, 102 }, {  51, 153, 102 }, {  51, 102, 102 },
250                    {  51,  51, 102 }, {  51,   0, 102 }, {  51, 255,  51 }, {  51, 204,  51 },
251                    {  51, 153,  51 }, {  51, 102,  51 }, {  51,  51,  51 }, {  51,   0,  51 },
252                    {  51, 255,   0 }, {  51, 204,   0 }, {  51, 153,   0 }, {  51, 102,   0 },
253                    {  51,  51,   0 }, {  51,   0,   0 }, {   0, 255, 102 }, {   0, 204, 102 },
254                    {   0, 153, 102 }, {   0, 102, 102 }, {   0,  51, 102 }, {   0,   0, 102 },
255                    {   0, 255,  51 }, {   0, 204,  51 }, {   0, 153,  51 }, {   0, 102,  51 },
256                    {   0,  51,  51 }, {   0,   0 , 51 }, {   0, 255,   0 }, {   0, 204,   0 },
257                    {   0, 153,   0 }, {   0, 102,   0 }, {   0,  51,   0 }, {  17,  17,  17 },
258                    {  34,  34,  34 }, {  68,  68,  68 }, {  85,  85,  85 }, { 119, 119, 119 },
259                    { 136, 136, 136 }, { 170, 170, 170 }, { 187, 187, 187 }, { 221, 221, 221 },
260                    { 238, 238, 238 }, { 192, 192, 192 }, { 128,   0,   0 }, { 128,   0, 128 },
261                    {   0, 128,   0 }, {   0, 128, 128 }, {   0,   0,   0 }, {   0,   0,   0 },
262                    {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
263                    {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
264                    {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
265                    {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
266                    {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 },
267                    {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }, {   0,   0,   0 }
268            };
269    
270            private int bitsPerPixel;
271            private int blueBits;
272            private int bytesPerRow;
273            private byte[] compressedRow;
274            private int compression;
275            private long compressedDataOffset;
276            private int compressedLength;
277            private int flags;
278            private int greenBits;
279            private int height;
280            private Palette palette;
281            private int nextImageOffset;
282            private int redBits;
283            private byte[] rgb;
284            private byte[] transColor;
285            private int transparencyIndex = -1;
286            private int version;
287            private int width;
288    
289            private static Palette createPalette(short[][] data)
290            {
291                    Palette result = new Palette(data.length);
292                    for (int i = 0; i < data.length; i++)
293                    {
294                            result.put(i, data[i][0], data[i][1], data[i][2]);
295                    }
296                    return result;
297            }
298    
299            /**
300             * Creates the 2 bits per pixel Palm system palette with grayscale values.
301             * This palette is used when no custom palette is defined in a 2 bpp image.
302             * @return Palm's default palette for 2 bits per pixel (grayscale), with 4 entries
303             */
304            public static Palette createSystem2BitGrayscalePalette()
305            {
306                    return createPalette(PALM_SYSTEM_PALETTE_4_GRAY);
307            }
308    
309            /**
310             * Creates the 4 bits per pixel Palm system palette with color values.
311             * This palette (or the 4 bpp grayscale palette) is used when no custom palette is defined in a 4 bpp image.
312             * @return Palm's default palette for 4 bits per pixel (color), with 16 entries
313             */
314            public static Palette createSystem4BitColorPalette()
315            {
316                    return createPalette(PALM_SYSTEM_PALETTE_16_COLOR);
317            }
318    
319            /**
320             * Creates the 4 bits per pixel Palm system palette with grayscale values.
321             * This palette (or the 4 bpp color palette) is used when no custom palette is defined in a 4 bpp image.
322             * @return Palm's default palette for 4 bits per pixel (grayscale), with 16 entries
323             */
324            public static Palette createSystem4BitGrayscalePalette()
325            {
326                    return createPalette(PALM_SYSTEM_PALETTE_16_GRAY);
327            }
328    
329            /**
330             * Creates the 8 bits per pixel Palm system palette.
331             * This palette is used when no custom palette is defined in an 8 bpp image.
332             * @return Palm's default palette for 8 bits per pixel, with 256 entries
333             */
334            public static Palette createSystem8BitPalette()
335            {
336                    return createPalette(PALM_SYSTEM_PALETTE_256);
337            }
338    
339            /**
340             * Returns the Palm compression method.
341             * This should be one of the COMPRESSION_xyz constants of this class.
342             * @return integer value with the compression method (found in a file when
343             *  loading or to be used for saving)
344             * @see #setCompression
345             */
346            public int getCompression()
347            {
348                    return compression;
349            }
350    
351            public String getFormatName()
352            {
353                    return "Palm image file format";
354            }
355    
356            public String[] getMimeTypes()
357            {
358                    return null;
359            }
360    
361            /**
362             * Returns the transpareny index if one is available ({@link #hasTransparencyIndex}
363             * returns <code>true</code>) or an undefined value otherwise.
364             * @see #hasTransparencyIndex
365             * @see #removeTransparencyIndex
366             * @see #setTransparencyIndex
367             */
368            public int getTransparencyIndex()
369            {
370                    return transparencyIndex;
371            }
372    
373            /**
374             * Returns whether a transpareny index is available and can be
375             * retrieved via {@link #getTransparencyIndex}.
376             * @return transparency index, a positive value that is a valid index into the palette
377             * @see #getTransparencyIndex
378             * @see #removeTransparencyIndex
379             * @see #setTransparencyIndex
380             */
381            public boolean hasTransparencyIndex()
382            {
383                    return transparencyIndex >= 0;
384            }
385    
386            private void invertBilevelData(byte[] row)
387            {
388                    if (row != null)
389                    {
390                            for (int i = 0; i < row.length; i++)
391                            {
392                                    row[i] = (byte)~row[i];
393                            }
394                    }
395            }
396    
397            private static boolean isEqualPalette(Palette palette, short[][] data)
398            {
399                    if (palette == null || data == null)
400                    {
401                            return false;
402                    }
403                    if (palette.getNumEntries() != data.length)
404                    {
405                            return false;
406                    }
407                    for (int i = 0; i < data.length; i++)
408                    {
409                            int red = palette.getSample(RGBIndex.INDEX_RED, i);
410                            int green = palette.getSample(RGBIndex.INDEX_GREEN, i);
411                            int blue = palette.getSample(RGBIndex.INDEX_BLUE, i);
412                            short[] color = data[i];
413                            if (color[0] != red || color[1] != green || color[2] != blue)
414                            {
415                                    return false;
416                            }
417                    }
418                    return true;
419            }
420    
421            public boolean isLoadingSupported()
422            {
423                    return true;
424            }
425    
426            /**
427             * Returns if the argument palette is the Palm system grayscale palette
428             * with 4 entries.
429             * @param palette to be checked
430             * @see #createSystem2BitGrayscalePalette
431             */
432            public static boolean isPalmSystemPaletteGray4(Palette palette)
433            {
434                    return isEqualPalette(palette, PALM_SYSTEM_PALETTE_4_GRAY);
435            }
436    
437            /**
438             * Returns if the argument palette is the Palm system grayscale palette
439             * with 16 entries.
440             * @param palette to be checked
441             * @see #createSystem4BitGrayscalePalette
442             */
443            public static boolean isPalmSystemPaletteGray16(Palette palette)
444            {
445                    return isEqualPalette(palette, PALM_SYSTEM_PALETTE_16_GRAY);
446            }
447    
448            /**
449             * Returns if the argument palette is the Palm system color palette
450             * with 16 entries.
451             * @param palette to be checked
452             * @see #createSystem4BitColorPalette
453             */
454            public static boolean isPalmSystemPaletteColor16(Palette palette)
455            {
456                    return isEqualPalette(palette, PALM_SYSTEM_PALETTE_16_COLOR);
457            }
458    
459            /**
460             * Returns if the argument palette is the Palm system palette
461             * with 256 colors.
462             * @param palette to be checked
463             * @see #createSystem8BitPalette
464             * @return if the argument is an 8 bits per pixel Palm system palette
465             */
466            public static boolean isPalmSystemPalette256(Palette palette)
467            {
468                    return isEqualPalette(palette, PALM_SYSTEM_PALETTE_256);
469            }
470    
471            public boolean isSavingSupported()
472            {
473                    return true;
474            }
475    
476            private void load() throws 
477                    InvalidFileStructureException, 
478                    IOException, 
479                    OperationFailedException,
480                    UnsupportedTypeException,
481                    WrongFileFormatException
482            {
483                    DataInput in = getInputAsDataInput();
484                    loadHeader(in);
485                    loadPalette(in);
486                    loadImage(in);
487            }
488    
489            private void loadHeader(DataInput in) throws 
490                    InvalidFileStructureException, 
491                    IOException, 
492                    UnsupportedTypeException,
493                    WrongFileFormatException
494            {
495                    width = in.readShort() & 0xffff;
496                    height = in.readShort() & 0xffff;
497                    bytesPerRow = in.readShort() & 0xffff;
498                    flags = in.readShort() & 0xffff;
499                    bitsPerPixel = in.readUnsignedByte();
500                    version = in.readUnsignedByte();
501                    nextImageOffset = in.readShort() & 0xffff;
502                    transparencyIndex = in.readUnsignedByte() & 0xffff;
503                    compression = in.readUnsignedByte() & 0xffff;
504                    in.skipBytes(2); // reserved
505                    if ((flags & FLAG_COMPRESSED) == 0)
506                    {
507                            compression = COMPRESSION_NONE;
508                    }
509                    boolean unsupportedDirectColor = false;
510                    if ((flags & FLAG_DIRECT_COLOR) != 0)
511                    {
512                            // read direct color information (8 bytes)
513                            redBits = in.readUnsignedByte();
514                            greenBits = in.readUnsignedByte();
515                            blueBits = in.readUnsignedByte();
516                            unsupportedDirectColor = redBits != 5 || greenBits != 6 || blueBits != 5;
517                            in.skipBytes(2);
518                            transColor = new byte[3];
519                            in.readFully(transColor);
520                    }
521                    if (width < 1 || height < 1 || 
522                        unsupportedDirectColor || 
523                        (bitsPerPixel != 1 && bitsPerPixel != 2 && bitsPerPixel != 4 && bitsPerPixel != 8 && bitsPerPixel != 16) ||
524                        (compression != COMPRESSION_NONE && compression != COMPRESSION_RLE && compression != COMPRESSION_SCANLINE))
525                    {
526                            throw new WrongFileFormatException("Not a file in Palm image file format.");
527                    }
528                    /*System.out.println("width=" + width + ", height=" + height + ", bytes per row=" +
529                            bytesPerRow + ", flags=" + flags + ", bpp=" + bitsPerPixel + ", version=" +
530                            version + ", palette=" + (((flags & FLAG_COLOR_TABLE) != 0) ? "y" : "n") +
531                            ", transparent=" + transparencyIndex + ", compression=" + compression);*/
532            }
533    
534            private void loadImage(DataInput in) throws 
535                    InvalidFileStructureException, 
536                    IOException, 
537                    UnsupportedTypeException,
538                    WrongFileFormatException,
539                    WrongParameterException
540            {
541                    setBoundsIfNecessary(width, height);
542                    checkBounds(width, height);
543                    PixelImage image = getImage();
544                    /* if there is no image to be reused (image == null), create one;
545                       otherwise check if the provided image is of the right type 
546                       and throw an exception if not */
547                    if (palette != null)
548                    {
549                            // paletted image
550                            if (image == null)
551                            {
552                                    image = new MemoryPaletted8Image(getBoundsWidth(), getBoundsHeight(), palette);
553                            }
554                            else
555                            {
556                                    if (!(image instanceof Paletted8Image))
557                                    {
558                                            throw new WrongParameterException("Image to be used for loading must be paletted for this file.");
559                                    }
560                                    ((Paletted8Image)image).setPalette(palette);
561                            }
562                    }
563                    else
564                    {
565                            switch(bitsPerPixel)
566                            {
567                                    case(1): // bilevel image (black and white)
568                                    {
569                                            if (image == null)
570                                            {
571                                                    image = new MemoryBilevelImage(getBoundsWidth(), getBoundsHeight());
572                                            }
573                                            else
574                                            {
575                                                    if (!(image instanceof BilevelImage))
576                                                    {
577                                                            throw new WrongParameterException("Image to be used for " +
578                                                                    "loading must implement BilevelImage for this file.");
579                                                    }
580                                            }
581                                            break;
582                                    }
583                                    case(16): // RGB direct color
584                                    {
585                                            if (image == null)
586                                            {
587                                                    image = new MemoryRGB24Image(getBoundsWidth(), getBoundsHeight());
588                                            }
589                                            else
590                                            {
591                                                    if (!(image instanceof RGB24Image))
592                                                    {
593                                                            throw new WrongParameterException("Image to be used for " +
594                                                                    "loading must implement RGB24Image.");
595                                                    }
596                                            }
597                                            rgb = new byte[width * 3];
598                                            break;
599                                    }
600                                    default: // grayscale, 2, 4 or 8 bits per pixel
601                                    {
602                                            if (image == null)
603                                            {
604                                                    image = new MemoryGray8Image(getBoundsWidth(), getBoundsHeight());
605                                            }
606                                            else
607                                            {
608                                                    if (!(image instanceof Gray8Image))
609                                                    {
610                                                            throw new WrongParameterException("Image to be used for " +
611                                                                    "loading must implement Gray8Image for this file.");
612                                                    }
613                                            }
614                                    }
615                            }
616                    }
617                    setImage(image);
618                    // check if image has the correct pixel resolution
619                    if (image.getWidth() != getBoundsWidth() || image.getHeight() != getBoundsHeight())
620                    {
621                            throw new WrongParameterException("Image to be reused has wrong resolution (must have " +
622                                    getBoundsWidth() + " x " + getBoundsHeight() + " pixels).");
623                    }
624                    loadImageData(in);
625            }
626    
627            private void loadImageData(DataInput in) throws
628                    InvalidFileStructureException,
629                    IOException
630            {
631                    PixelImage image = getImage();
632                    // if compression is used, read a short with the compressed data size
633                    if (compression != COMPRESSION_NONE)
634                    {
635                            int compressedDataSize = in.readShort() & 0xffff;
636                    }
637                    byte[] row = new byte[bytesPerRow];
638                    final int NUM_ROWS = getBoundsY2() + 1;
639                    for (int y = 0; y < NUM_ROWS; y++)
640                    {
641                            switch(compression)
642                            {
643                                    case(COMPRESSION_NONE):
644                                    {
645                                            in.readFully(row, 0, bytesPerRow);
646                                            break;
647                                    }
648                                    case(COMPRESSION_RLE):
649                                    {
650                                            int index = 0;
651                                            do
652                                            {
653                                                    int num = in.readUnsignedByte();
654                                                    if (num < 1 || index + num > bytesPerRow)
655                                                    {
656                                                            String message = "At index=" + index + ", y=" + y + " there is a run length of " + num;
657                                                            System.err.println("ERROR decoding RLE: " + message);
658                                                            throw new InvalidFileStructureException(message);
659                                                    }
660                                                    byte value = in.readByte();
661                                                    while (num-- > 0)
662                                                    {
663                                                            row[index++] = value;
664                                                    }
665                                            }
666                                            while (index < bytesPerRow);
667                                            break;
668                                    }
669                                    case(COMPRESSION_SCANLINE):
670                                    {
671                                            int index = 0;
672                                            int pixelMask = 0;
673                                            int mask = 0;
674                                            do
675                                            {
676                                                    if (mask == 0)
677                                                    {
678                                                            pixelMask = in.readUnsignedByte();
679                                                            mask = 0x80;
680                                                    }
681                                                    if ((pixelMask & mask) == 0)
682                                                    {
683                                                            index++;
684                                                    }
685                                                    else
686                                                    {
687                                                            row[index++] = in.readByte();
688                                                    }
689                                                    mask >>= 1;
690                                            }
691                                            while (index < bytesPerRow);
692                                            break;
693                                    }
694                                    case(COMPRESSION_PACKBITS):
695                                    {
696                                            // compression algorithm unknown, thus not implemented
697                                            // this statement cannot be reached, the codec makes
698                                            // sure that an exception gets thrown when the packbits
699                                            // algorithm is actually encountered in a file;
700                                            // if you have a description of the algorithm, please
701                                            // contact the JIU maintainers
702                                            break;
703                                    }
704                            }
705                            store(image, y, row);
706                            setProgress(y, NUM_ROWS);
707                    }
708            }
709    
710            private void loadPalette(DataInput in) throws 
711                    InvalidFileStructureException, 
712                    IOException, 
713                    UnsupportedTypeException,
714                    WrongFileFormatException
715            {
716                    if ((flags & FLAG_COLOR_TABLE) == 0)
717                    {
718                            switch(bitsPerPixel)
719                            {
720                                    case(2):
721                                    {
722                                            palette = createSystem2BitGrayscalePalette();
723                                            break;
724                                    }
725                                    case(4):
726                                    {
727                                            palette = createSystem4BitGrayscalePalette(); // or color?
728                                            break;
729                                    }
730                                    case(8):
731                                    {
732                                            palette = createSystem8BitPalette();
733                                            break;
734                                    }
735                            }
736                            return;
737                    }
738                    int numEntries = in.readShort() & 0xffff;
739                    if (numEntries < 1 || numEntries > 256)
740                    {
741                            throw new WrongFileFormatException("Not a Palm image file, invalid number of palette entries: "  + numEntries);
742                    }
743                    palette = new Palette(numEntries, 255);
744                    for (int i = 0; i < numEntries; i++)
745                    {
746                            int reserved = in.readUnsignedByte();
747                            int red = in.readUnsignedByte();
748                            int green = in.readUnsignedByte();
749                            int blue = in.readUnsignedByte();
750                            palette.putSample(RGBIndex.INDEX_RED, i, red);
751                            palette.putSample(RGBIndex.INDEX_GREEN, i, green);
752                            palette.putSample(RGBIndex.INDEX_BLUE, i, blue);
753                    }
754            }
755    
756            public void process() throws 
757                    InvalidFileStructureException,
758                    MissingParameterException,
759                    OperationFailedException,
760                    WrongParameterException
761            {
762                    try
763                    {
764                            initModeFromIOObjects();
765                            if (getMode() == CodecMode.LOAD)
766                            {
767                                    load();
768                            }
769                            else
770                            if (getMode() == CodecMode.SAVE)
771                            {
772                                    save();
773                            }
774                            else
775                            {
776                                    throw new WrongParameterException("Could find neither objects for loading nor for saving.");
777                            }
778                    }
779                    catch (IOException ioe)
780                    {
781                            throw new OperationFailedException("I/O error in Palm codec: " + ioe.toString());
782                    }
783            }
784    
785            /**
786             * Removes the transparency index if one has been set.
787             * @see #getTransparencyIndex
788             * @see #hasTransparencyIndex
789             * @see #setTransparencyIndex
790             */
791            public void removeTransparencyIndex()
792            {
793                    transparencyIndex = -1;
794            }
795    
796            private void save() throws 
797                    IOException, 
798                    OperationFailedException,
799                    UnsupportedTypeException
800            {
801                    // get image, set bounds if necessary and check existing bounds
802                    PixelImage image = getImage();
803                    if (image == null)
804                    {
805                            throw new MissingParameterException("Need image to save.");
806                    }
807                    setBoundsIfNecessary(image.getWidth(), image.getHeight());
808                    checkBounds(image.getWidth(), image.getHeight());
809                    // get output object
810                    DataOutput out = getOutputAsDataOutput();
811                    if (out == null)
812                    {
813                            throw new MissingParameterException("Could not get DataOutput object when saving in Palm file format.");
814                    }
815                    // initialize fields to be written to the header
816                    width = getBoundsWidth();
817                    height = getBoundsHeight();
818                    flags = 0;
819                    if (hasTransparencyIndex())
820                    {
821                            flags |= FLAG_TRANSPARENCY;
822                    }
823                    if (compression != COMPRESSION_NONE)
824                    {
825                            flags |= FLAG_COMPRESSED;
826                    }
827                    version = 0;
828                    if (bitsPerPixel > 1)
829                    {
830                            version = 1;
831                    }
832                    if (hasTransparencyIndex() || compression != COMPRESSION_NONE)
833                    {
834                            version = 2;
835                    }
836                    nextImageOffset = 0;
837                    compressedDataOffset = 0;
838                    // check image types
839                    if (image instanceof BilevelImage)
840                    {
841                            save(out, (BilevelImage)image);
842                    }
843                    else
844                    if (image instanceof Gray8Image)
845                    {
846                            save(out, (Gray8Image)image);
847                    }
848                    else
849                    if (image instanceof Paletted8Image)
850                    {
851                            save(out, (Paletted8Image)image);
852                    }
853                    else
854                    if (image instanceof RGB24Image)
855                    {
856                            save(out, (RGB24Image)image);
857                    }
858                    else
859                    {
860                            throw new UnsupportedTypeException("Unsupported image type: " + image.getClass().getName());
861                    }
862            }
863    
864            private void save(DataOutput out, BilevelImage image) throws IOException
865            {
866                    bytesPerRow = (width + 7) / 8;
867                    if ((bytesPerRow % 2) == 1)
868                    {
869                            bytesPerRow++;
870                    }
871                    bitsPerPixel = 1;
872                    setCorrectVersion();
873                    saveHeader(out);
874                    byte[] row = new byte[bytesPerRow];
875                    byte[] prev = null;
876                    if (compression == COMPRESSION_SCANLINE)
877                    {
878                            prev = new byte[row.length];
879                    }
880                    final int X1 = getBoundsX1();
881                    final int Y1 = getBoundsY1();
882                    for (int y = 0; y < height; y++)
883                    {
884                            image.getPackedBytes(X1, y + Y1, width, row, 0, 0);
885                            invertBilevelData(row);
886                            saveRow(out, y == 0, row, prev);
887                            if (compression == COMPRESSION_SCANLINE)
888                            {
889                                    System.arraycopy(row, 0, prev, 0, row.length);
890                            }
891                            setProgress(y, height);
892                    }
893                    saveFinalCompressedSize(out);
894            }
895    
896            private void save(DataOutput out, Gray8Image image) throws IOException
897            {
898                    bytesPerRow = width;
899                    if ((bytesPerRow % 2) == 1)
900                    {
901                            bytesPerRow++;
902                    }
903                    bitsPerPixel = 8;
904                    flags |= FLAG_COLOR_TABLE;
905                    setCorrectVersion();
906                    saveHeader(out);
907                    out.writeShort(256); // palette length
908                    for (int i = 0; i < 256; i++)
909                    {
910                            out.writeByte(0); // reserved
911                            out.writeByte(i); // red
912                            out.writeByte(i); // green
913                            out.writeByte(i); // blue
914                    }
915                    compressedDataOffset += 2 + 4 * 256;
916                    saveInitialCompressedSize(out);
917                    byte[] row = new byte[width];
918                    byte[] prev = null;
919                    if (compression == COMPRESSION_SCANLINE)
920                    {
921                            prev = new byte[width];
922                    }
923                    final int X1 = getBoundsX1();
924                    final int Y1 = getBoundsY1();
925                    for (int y = 0; y < height; y++)
926                    {
927                            image.getByteSamples(0, X1, y + Y1, width, 1, row, 0);
928                            saveRow(out, y == 0, row, prev);
929                            if (compression == COMPRESSION_SCANLINE)
930                            {
931                                    System.arraycopy(row, 0, prev, 0, row.length);
932                            }
933                            setProgress(y, height);
934                    }
935                    saveFinalCompressedSize(out);
936            }
937    
938            private void save(DataOutput out, Paletted8Image image) throws IOException
939            {
940                    Palette palette = image.getPalette();
941                    boolean system256Palette = isPalmSystemPalette256(palette);
942                    boolean system16GrayPalette = isPalmSystemPaletteGray16(palette);
943                    boolean system16ColorPalette = isPalmSystemPaletteColor16(palette);
944                    boolean system4GrayPalette = isPalmSystemPaletteGray4(palette);
945                    boolean customPalette = !(system256Palette || system16GrayPalette || system16ColorPalette || system4GrayPalette);
946                    if (customPalette)
947                    {
948                            flags |= FLAG_COLOR_TABLE;
949                    }
950                    // determine bits per pixel, bytesPerRow
951                    if (palette.getNumEntries() <= 4)
952                    {
953                            bitsPerPixel = 2;
954                            bytesPerRow = (width + 3) / 4;
955                    }
956                    else
957                    if (palette.getNumEntries() <= 16)
958                    {
959                            bitsPerPixel = 4;
960                            bytesPerRow = (width + 1) / 2;
961                    }
962                    else
963                    {
964                            bitsPerPixel = 8;
965                            bytesPerRow = width;
966                    }
967                    //System.out.println("initial bytesPerRow=" + bytesPerRow);
968                    // make sure number of bytes per row is even
969                    if ((bytesPerRow % 2) == 1)
970                    {
971                            bytesPerRow++;
972                    }
973                    setCorrectVersion();
974                    saveHeader(out);
975                    // write the custom palette if necessary
976                    if (customPalette)
977                    {
978                            savePalette(out, palette);
979                    }
980                    // if compression type != uncompressed write two bytes with compressed size to output
981                    saveInitialCompressedSize(out);
982                    // initialize row buffers
983                    byte[] row = new byte[width];
984                    byte[] prev = null;
985                    if (compression == COMPRESSION_SCANLINE)
986                    {
987                            prev = new byte[row.length];
988                    }
989                    byte[] temp = null;
990                    if (bitsPerPixel < 8)
991                    {
992                            temp = new byte[width];
993                    }
994                    // get position of upper left corner of image part to be written
995                    final int X1 = getBoundsX1();
996                    final int Y1 = getBoundsY1();
997                    // write all rows to file, top to bottom
998                    for (int y = 0; y < height; y++)
999                    {
1000                            switch(bitsPerPixel)
1001                            {
1002                                    case(2):
1003                                    {
1004                                            image.getByteSamples(0, X1, y + Y1, width, 1, temp, 0);
1005                                            ArrayConverter.encodePacked2Bit(temp, 0, row, 0, width);
1006                                            break;
1007                                    }
1008                                    case(4):
1009                                    {
1010                                            image.getByteSamples(0, X1, y + Y1, width, 1, temp, 0);
1011                                            ArrayConverter.encodePacked4Bit(temp, 0, row, 0, width);
1012                                            break;
1013                                    }
1014                                    case(8):
1015                                    {
1016                                            image.getByteSamples(0, X1, y + Y1, width, 1, row, 0);
1017                                            break;
1018                                    }
1019                            }
1020                            saveRow(out, y == 0, row, prev);
1021                            if (compression == COMPRESSION_SCANLINE)
1022                            {
1023                                    System.arraycopy(row, 0, prev, 0, row.length);
1024                            }
1025                            setProgress(y, height);
1026                    }
1027                    saveFinalCompressedSize(out);
1028            }
1029    
1030            private void save(DataOutput out, RGB24Image image) throws IOException
1031            {
1032                    bytesPerRow = width * 2;
1033                    bitsPerPixel = 16;
1034                    flags |= FLAG_DIRECT_COLOR;
1035                    setCorrectVersion();
1036                    saveHeader(out);
1037                    // write 8 bytes for direct color information to file
1038                    out.write(5); // red bits
1039                    out.write(6); // green bits
1040                    out.write(5); // blue bits
1041                    int i = 5;
1042                    while (i-- > 0)
1043                    {
1044                            out.write(0);
1045                    }
1046                    compressedDataOffset += 8;
1047                    // allocate row buffer(s)
1048                    byte[] row = new byte[width * 2];
1049                    byte[] prev = null;
1050                    if (compression == COMPRESSION_SCANLINE)
1051                    {
1052                            prev = new byte[row.length];
1053                    }
1054                    byte[] red = new byte[width];
1055                    byte[] green = new byte[width];
1056                    byte[] blue = new byte[width];
1057                    
1058                    final int X1 = getBoundsX1();
1059                    final int Y1 = getBoundsY1();
1060                    for (int y = 0; y < height; y++)
1061                    {
1062                            // get samples for each channel of the row to be written out
1063                            image.getByteSamples(RGBIndex.INDEX_RED, X1, y + Y1, width, 1, red, 0);
1064                            image.getByteSamples(RGBIndex.INDEX_GREEN, X1, y + Y1, width, 1, green, 0);
1065                            image.getByteSamples(RGBIndex.INDEX_BLUE, X1, y + Y1, width, 1, blue, 0);
1066                            // encode row as 16 bit samples, big endian, 5-6-5
1067                            ArrayConverter.encodeRGB24ToPackedRGB565BigEndian(
1068                                    red, 0,
1069                                    green, 0,
1070                                    blue, 0,
1071                                    row, 0,
1072                                    width);
1073                            saveRow(out, y == 0, row, prev);
1074                            if (compression == COMPRESSION_SCANLINE)
1075                            {
1076                                    System.arraycopy(row, 0, prev, 0, row.length);
1077                            }
1078                            setProgress(y, height);
1079                    }
1080                    saveFinalCompressedSize(out);
1081            }
1082    
1083            private void saveFinalCompressedSize(DataOutput out) throws IOException
1084            {
1085                    if ((flags & FLAG_COMPRESSED) == 0)
1086                    {
1087                            return;
1088                    }
1089                    if (!(out instanceof RandomAccessFile || out instanceof SeekableByteArrayOutputStream))
1090                    {
1091                            return;
1092                    }
1093                    long pos = -1;
1094                    if (out instanceof RandomAccessFile)
1095                    {
1096                            RandomAccessFile raf = (RandomAccessFile)out;
1097                            pos = raf.length();
1098                    }
1099                    else
1100                    if (out instanceof SeekableByteArrayOutputStream)
1101                    {
1102                            SeekableByteArrayOutputStream sbaos = (SeekableByteArrayOutputStream)out;
1103                            pos = sbaos.getPosition();
1104                    }
1105                    long compressedSize = pos - compressedDataOffset;
1106                    compressedSize = Math.min(0xffff, compressedSize);
1107                    /*
1108                    System.out.println("compressed data offset=" + compressedDataOffset);
1109                    System.out.println("position after compression=" + pos);
1110                    System.out.println("compressed size=" + compressedSize + " / " + Integer.toHexString((int)compressedSize));
1111                    */
1112                    if (out instanceof RandomAccessFile)
1113                    {
1114                            RandomAccessFile raf = (RandomAccessFile)out;
1115                            raf.seek(compressedDataOffset);
1116                            raf.writeShort((int)compressedSize);
1117                    }
1118                    else
1119                    if (out instanceof SeekableByteArrayOutputStream)
1120                    {
1121                            SeekableByteArrayOutputStream sbaos = (SeekableByteArrayOutputStream)out;
1122                            sbaos.seek((int)compressedDataOffset);
1123                            sbaos.write((int)(compressedSize >> 8) & 0xff);
1124                            sbaos.write((int)compressedSize & 0xff);
1125                    }
1126            }
1127    
1128            private void saveHeader(DataOutput out) throws IOException
1129            {
1130                    out.writeShort(width);
1131                    out.writeShort(height);
1132                    out.writeShort(bytesPerRow);
1133                    out.writeShort(flags);
1134                    out.writeByte(bitsPerPixel);
1135                    out.writeByte(version);
1136                    out.writeShort(0); // next image offset
1137                    out.writeByte(transparencyIndex);
1138                    out.writeByte(compression);
1139                    out.writeShort(0); // reserved
1140                    compressedDataOffset = 16;
1141            }
1142    
1143            private void saveInitialCompressedSize(DataOutput out) throws IOException
1144            {
1145                    if ((flags & FLAG_COMPRESSED) == 0)
1146                    {
1147                            return;
1148                    }
1149                    out.writeShort(bytesPerRow * height); // just a guess
1150            }
1151    
1152            private void savePalette(DataOutput out, Palette palette) throws IOException
1153            {
1154                    out.writeShort(palette.getNumEntries());
1155                    for (int i = 0; i < palette.getNumEntries(); i++)
1156                    {
1157                            out.writeByte(0); // reserved
1158                            out.writeByte(palette.getSample(RGBIndex.INDEX_RED, i));
1159                            out.writeByte(palette.getSample(RGBIndex.INDEX_GREEN, i));
1160                            out.writeByte(palette.getSample(RGBIndex.INDEX_BLUE, i));
1161                    }
1162                    compressedDataOffset += 2 + 4 * palette.getNumEntries();
1163            }
1164    
1165            private void saveRow(DataOutput out, boolean firstRow, byte[] row, byte[] prev) throws IOException
1166            {
1167                    switch(compression)
1168                    {
1169                            case(COMPRESSION_NONE):
1170                            {
1171                                    out.write(row, 0, bytesPerRow);
1172                                    break;
1173                            }
1174                            case(COMPRESSION_RLE):
1175                            {
1176                                    saveRowRLE(out, row);
1177                                    break;
1178                            }
1179                            case(COMPRESSION_SCANLINE):
1180                            {
1181                                    saveRowScanLine(out, firstRow, row, prev);
1182                                    break;
1183                            }
1184                    }
1185            }
1186    
1187    /*              int srcOffset = 0; // points into uncompressed data array "row"
1188                    do
1189                    {
1190                            // determine length of next run, between 1 and 255
1191                            byte value = row[srcOffset];
1192                            int lookAheadOffset = srcOffset + 1;
1193                            int bytesLeft = bytesPerRow - lookAheadOffset;
1194                            if (bytesLeft > 255)
1195                            {
1196                                    bytesLeft = 255;
1197                            }
1198                            while (bytesLeft != 0 && value == row[lookAheadOffset])
1199                            {
1200                                    lookAheadOffset++;
1201                                    bytesLeft--;
1202                            }
1203                            int runLength = lookAheadOffset - srcOffset;
1204                            if (runLength < 1)
1205                            {
1206                                    System.err.println("FATAL: RUN LENGTH <0");
1207                                    System.exit(1);
1208                            }
1209                            if (srcOffset + runLength > bytesPerRow)
1210                            {
1211                                    System.err.println("FATAL: srcOffset=" + srcOffset+ " runLength=" + runLength + " bytesPerRow=" + bytesPerRow);
1212                                    System.exit(1);
1213                            }
1214                            if (srcOffset == 13 && runLength == 2)
1215                            {
1216                                    System.err.println("FATAL: 13 2 ");
1217                                    System.exit(1);
1218                            }
1219                            // write pair (length-of-run, value) to output
1220                            out.writeByte(runLength);
1221                            out.writeByte(value & 0xff);
1222                            // update srcOffset to point to the next byte in row to be encoded
1223                            srcOffset += runLength;
1224                    }
1225                    while (srcOffset < bytesPerRow);*/
1226    
1227            private void saveRowRLE(DataOutput out, byte[] row) throws IOException
1228            {
1229                    int srcOffset = 0; // points into uncompressed data array "row"
1230                    do
1231                    {
1232                            // determine length of next run, between 1 and 255
1233                            int runLength = 1;
1234                            int bytesLeft = bytesPerRow - srcOffset;
1235                            byte value = row[srcOffset];
1236                            while (bytesLeft != 0 && srcOffset + runLength < row.length && value == row[srcOffset + runLength])
1237                            {
1238                                    bytesLeft--;
1239                                    runLength++;
1240                                    if (runLength == 255)
1241                                    {
1242                                            bytesLeft = 0;
1243                                    }
1244                            }
1245                            srcOffset += runLength;
1246                            out.writeByte(runLength);
1247                            out.writeByte(value & 0xff);
1248                    }
1249                    while (srcOffset < bytesPerRow);
1250            }
1251    
1252            private void saveRowScanLine(DataOutput out, boolean firstRow, byte[] row, byte[] prev) throws IOException
1253            {
1254                    int bytesLeft = bytesPerRow;
1255                    int srcOffset = 0;
1256                    byte[] bytes = new byte[8];
1257                    do
1258                    {
1259                            int pixelMask = 0;
1260                            int bitMask = 128;
1261                            int numBytesToCheck = Math.min(8, bytesLeft);
1262                            int numOutputBytes = 0;
1263                            bytesLeft -= numBytesToCheck;
1264                            while (numBytesToCheck-- != 0)
1265                            {
1266                                    if (row[srcOffset] != prev[srcOffset])
1267                                    {
1268                                            pixelMask |= bitMask;
1269                                            bytes[numOutputBytes++] = row[srcOffset];
1270                                    }
1271                                    srcOffset++;
1272                                    bitMask >>= 1;
1273                            }
1274                            out.writeByte(pixelMask);
1275                            out.write(bytes, 0, numOutputBytes);
1276                    }
1277                    while (bytesLeft != 0);
1278            }
1279    
1280            /**
1281             * Sets the compression algorithm to be used for saving an image.
1282             * @see #getCompression
1283             * @param newCompressionType int value that is one of the COMPRESSION_xyz constants of this class
1284             * @throws IllegalArgumentException if the compression type is unsupported
1285             */
1286            public void setCompression(int newCompressionType)
1287            {
1288                    if (newCompressionType != COMPRESSION_NONE && 
1289                        newCompressionType != COMPRESSION_RLE &&
1290                        newCompressionType != COMPRESSION_SCANLINE)
1291                    {
1292                            throw new IllegalArgumentException("Unsupported Palm compression type for writing.");
1293                    }
1294                    compression = newCompressionType;
1295            }
1296    
1297            private void setCorrectVersion()
1298            {
1299                    version = 0;
1300                    if (bitsPerPixel > 1)
1301                    {
1302                            version = 1;
1303                    }
1304                    if (hasTransparencyIndex() || getCompression() == COMPRESSION_SCANLINE || getCompression() == COMPRESSION_RLE)
1305                    {
1306                            version = 2;
1307                    }
1308            }
1309    
1310            /**
1311             * Reuses super.setFile when used for CodecMode.LOAD, but
1312             * creates a RandomAccessFile instead of a FileOutputStream
1313             * in write mode so that the compressed size can be written
1314             * correcly (requires a seek operation).
1315             * @param fileName name of the file to be opened
1316             * @param codecMode defines whether this codec object is to be used for loading or saving
1317             */
1318            public void setFile(String fileName, CodecMode codecMode) throws
1319                    IOException,
1320                    UnsupportedCodecModeException
1321            {
1322                    if (codecMode == CodecMode.LOAD)
1323                    {
1324                            super.setFile(fileName, codecMode);
1325                    }
1326                    else
1327                    {
1328                            setRandomAccessFile(new RandomAccessFile(fileName, "rw"), CodecMode.SAVE);
1329                    }
1330            }
1331                            
1332            /**
1333             * Sets a new transparency index when saving an image.
1334             * If this method is called, the argument value is used as an index
1335             * into the palette for a color that is supposed to be transparent.
1336             * When the resulting Palm image file is drawn onto some background,
1337             * all pixels in the color pointed to by the transparency index are not
1338             * supposed to be overdrawn so that the background is visisble at
1339             * those places.
1340             * @param newIndex the new transparency index, must be smaller than the number of entries in the palette
1341             * @see #getTransparencyIndex
1342             * @see #hasTransparencyIndex
1343             * @see #removeTransparencyIndex
1344             */
1345            public void setTransparencyIndex(int newIndex)
1346            {
1347                    if (newIndex < 0)
1348                    {
1349                            throw new IllegalArgumentException("Transparency index must be 0 or larger.");
1350                    }
1351                    transparencyIndex = newIndex;
1352            }
1353    
1354            private void store(PixelImage image, int y, byte[] row)
1355            {
1356                    if (!isRowRequired(y))
1357                    {
1358                            return;
1359                    }
1360                    y -= getBoundsY1();
1361                    switch(bitsPerPixel)
1362                    {
1363                            case(1):
1364                            {
1365                                    BilevelImage bimage = (BilevelImage)image;
1366                                    invertBilevelData(row);
1367                                    bimage.putPackedBytes(0, y, getBoundsWidth(), row, getBoundsX1() / 8, getBoundsX1() % 8);
1368                                    break;
1369                            }
1370                            case(2):
1371                            {
1372                                    byte[] dest = new byte[bytesPerRow * 4];
1373                                    ArrayConverter.decodePacked2Bit(row, 0, dest, 0, bytesPerRow);
1374                                    ByteChannelImage bcimg = (ByteChannelImage)image;
1375                                    bcimg.putByteSamples(0, 0, y, getBoundsWidth(), 1, dest, getBoundsX1());
1376                                    break;
1377                            }
1378                            case(4):
1379                            {
1380                                    byte[] dest = new byte[bytesPerRow * 2];
1381                                    ArrayConverter.decodePacked4Bit(row, 0, dest, 0, bytesPerRow);
1382                                    ByteChannelImage bcimg = (ByteChannelImage)image;
1383                                    bcimg.putByteSamples(0, 0, y, getBoundsWidth(), 1, dest, getBoundsX1());
1384                                    break;
1385                            }
1386                            case(8):
1387                            {
1388                                    ByteChannelImage bcimg = (ByteChannelImage)image;
1389                                    bcimg.putByteSamples(0, 0, y, getBoundsWidth(), 1, row, getBoundsX1());
1390                                    break;
1391                            }
1392                            case(16):
1393                            {
1394                                    ArrayConverter.decodePackedRGB565BigEndianToRGB24(
1395                                            row, getBoundsX1() * 2,
1396                                            rgb, 0,
1397                                            rgb, width,
1398                                            rgb, width * 2,
1399                                            getBoundsWidth());
1400                                    RGB24Image img = (RGB24Image)image;
1401                                    img.putByteSamples(RGBIndex.INDEX_RED, 0, y, getBoundsWidth(), 1, rgb, 0);
1402                                    img.putByteSamples(RGBIndex.INDEX_GREEN, 0, y, getBoundsWidth(), 1, rgb, width);
1403                                    img.putByteSamples(RGBIndex.INDEX_BLUE, 0, y, getBoundsWidth(), 1, rgb, width * 2);
1404                                    break;
1405                            }
1406                    }
1407            }
1408    
1409            public String suggestFileExtension(PixelImage image)
1410            {
1411                    return ".palm";
1412            }
1413    }