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 }