001 /* 002 * RASCodec 003 * 004 * Copyright (c) 2000, 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 net.sourceforge.jiu.codecs.ImageCodec; 014 import net.sourceforge.jiu.codecs.InvalidFileStructureException; 015 import net.sourceforge.jiu.codecs.UnsupportedTypeException; 016 import net.sourceforge.jiu.codecs.WrongFileFormatException; 017 import net.sourceforge.jiu.data.BilevelImage; 018 import net.sourceforge.jiu.data.Gray8Image; 019 import net.sourceforge.jiu.data.IntegerImage; 020 import net.sourceforge.jiu.data.MemoryPaletted8Image; 021 import net.sourceforge.jiu.data.MemoryRGB24Image; 022 import net.sourceforge.jiu.data.Palette; 023 import net.sourceforge.jiu.data.Paletted8Image; 024 import net.sourceforge.jiu.data.PixelImage; 025 import net.sourceforge.jiu.data.RGB24Image; 026 import net.sourceforge.jiu.data.RGBIndex; 027 import net.sourceforge.jiu.ops.OperationFailedException; 028 import net.sourceforge.jiu.ops.WrongParameterException; 029 import net.sourceforge.jiu.util.ArrayConverter; 030 031 /** 032 * A codec to read and write Sun Raster (RAS) image files. 033 * The typical file extension for this format is <code>.ras</code>. 034 * <h3>Usage example</h3> 035 * This code snippet demonstrate how to read a RAS file. 036 * <pre> 037 * RASCodec codec = new RASCodec(); 038 * codec.setFile("image.ras", CodecMode.LOAD); 039 * codec.process(); 040 * PixelImage loadedImage = codec.getImage(); 041 * </pre> 042 * <h3>Supported file types when reading</h3> 043 * Only uncompressed RAS files are read. 044 * Only 8 bit (gray and paletted) and 24 bit are supported when reading. 045 * <h3>Supported image types when writing</h3> 046 * Only {@link net.sourceforge.jiu.data.Paletted8Image} / uncompressed is supported when writing. 047 * <h3>Bounds</h3> 048 * The bounds concept of ImageCodec is supported so that you can load or save only part of an image. 049 * <h3>File format documentation</h3> 050 * This file format is documented as a man page <code>rasterfile(5)</code> on Sun Unix systems. 051 * That documentation can also be found online, e.g. at 052 * <a target="_top" href="http://www.doc.ic.ac.uk/~mac/manuals/sunos-manual-pages/sunos4/usr/man/man5/rasterfile.5.html">http://www.doc.ic.ac.uk/~mac/manuals/sunos-manual-pages/sunos4/usr/man/man5/rasterfile.5.html</a>. 053 * A <a target="_top" href="http://www.google.com/search?q=rasterfile%285%29&sourceid=opera&num=0">web search for rasterfile(5)</a> 054 * brings up other places as well. 055 * 056 * @author Marco Schmidt 057 */ 058 public class RASCodec extends ImageCodec 059 { 060 private static final int RAS_MAGIC = 0x59a66a95; 061 private static final int COMPRESSION_NONE = 0x00000001; 062 private static final int RAS_HEADER_SIZE = 32; 063 private int width; 064 private int height; 065 private int depth; 066 private int length; 067 private int type; 068 private int mapType; 069 private int mapLength; 070 private int bytesPerRow; 071 private int paddingBytes; 072 private int numColors; 073 private DataInput in; 074 private DataOutput out; 075 private Palette palette; 076 077 public String getFormatName() 078 { 079 return "Sun Raster (RAS)"; 080 } 081 082 public String[] getMimeTypes() 083 { 084 return new String[] {"image/x-ras"}; 085 } 086 087 public boolean isLoadingSupported() 088 { 089 return true; 090 } 091 092 public boolean isSavingSupported() 093 { 094 return true; 095 } 096 097 /** 098 * Loads an image from an RAS input stream. 099 * It is assumed that a stream was given to this codec using {@link #setInputStream(InputStream)}. 100 * 101 * @return the image as an instance of a class that implements {@link IntegerImage} 102 * @throws InvalidFileStructureException if the input stream is corrupt 103 * @throws java.io.IOException if there were problems reading from the input stream 104 * @throws UnsupportedTypeException if an unsupported flavor of the RAS format is encountered 105 * @throws WrongFileFormatException if this is not a valid RAS stream 106 */ 107 private void load() throws 108 IOException, 109 OperationFailedException 110 { 111 in = getInputAsDataInput(); 112 readHeader(); 113 readImage(); 114 } 115 116 public void process() throws OperationFailedException 117 { 118 try 119 { 120 initModeFromIOObjects(); 121 if (getMode() == CodecMode.LOAD) 122 { 123 load(); 124 } 125 else 126 if (getMode() == CodecMode.SAVE) 127 { 128 save(); 129 } 130 else 131 { 132 throw new WrongParameterException("Could find neither objects for loading nor for saving."); 133 } 134 } 135 catch (IOException ioe) 136 { 137 throw new OperationFailedException("I/O error in RAS codec: " + ioe.toString()); 138 } 139 } 140 141 private void readHeader() throws 142 InvalidFileStructureException, 143 UnsupportedTypeException, 144 WrongFileFormatException, 145 WrongParameterException, 146 java.io.IOException 147 { 148 byte[] header = new byte[RAS_HEADER_SIZE]; 149 in.readFully(header); 150 int magic = ArrayConverter.getIntBE(header, 0); 151 if (magic != RAS_MAGIC) 152 { 153 throw new WrongFileFormatException("This stream is not a valid " + 154 "Sun RAS stream (bad magic: " + Integer.toHexString(magic) + 155 " instead of " + Integer.toHexString(RAS_MAGIC)); 156 } 157 width = ArrayConverter.getIntBE(header, 4); 158 height = ArrayConverter.getIntBE(header, 8); 159 if (width < 1 || height < 1) 160 { 161 throw new InvalidFileStructureException("Width and height must both " + 162 "be larger than zero; found width=" + width + ", height=" + 163 height + "."); 164 } 165 setBoundsIfNecessary(width, height); 166 checkBounds(width, height); 167 depth = ArrayConverter.getIntBE(header, 12); 168 switch (depth) 169 { 170 case(1): 171 { 172 bytesPerRow = (width + 7) / 8; 173 break; 174 } 175 case(8): 176 { 177 bytesPerRow = width; 178 break; 179 } 180 case(24): 181 { 182 bytesPerRow = width * 3; 183 break; 184 } 185 default: 186 { 187 throw new UnsupportedTypeException("Depths other than 1, 8 and 24 " + 188 "unsupported when reading RAS stream; found " + depth); 189 } 190 } 191 paddingBytes = (bytesPerRow % 2); 192 numColors = 1 << depth; 193 //length = ArrayConverter.getIntBE(header, 16); 194 type = ArrayConverter.getIntBE(header, 20); 195 if (type != COMPRESSION_NONE) 196 { 197 throw new UnsupportedTypeException("Only uncompressed " + 198 "RAS streams are read; found " + type); 199 } 200 mapType = ArrayConverter.getIntBE(header, 24); 201 mapLength = ArrayConverter.getIntBE(header, 28); 202 if (mapLength != 0) 203 { 204 if (depth != 8) 205 { 206 throw new UnsupportedTypeException("Cannot handle Sun RAS " + 207 "input streams with color maps and a depth other than " + 208 "8 (found " + depth + ")."); 209 } 210 if (mapLength != 768) 211 { 212 throw new UnsupportedTypeException("Cannot handle Sun RAS " + 213 "input streams with color maps of a length different " + 214 "than 768; found " + mapLength); 215 } 216 if (mapType != 1) 217 { 218 throw new UnsupportedTypeException("Cannot handle Sun RAS " + 219 "input streams with color maps of a type other than " + 220 "1; found " + mapType); 221 } 222 palette = readPalette(); 223 } 224 else 225 { 226 palette = null; 227 } 228 } 229 230 private IntegerImage readImage() throws 231 InvalidFileStructureException, 232 java.io.IOException 233 { 234 RGB24Image rgb24Image = null; 235 Paletted8Image paletted8Image = null; 236 IntegerImage result = null; 237 int numChannels = 1; 238 int bytesPerRow = 0; 239 switch(depth) 240 { 241 case(8): 242 { 243 paletted8Image = new MemoryPaletted8Image(width, height, palette); 244 result = paletted8Image; 245 numChannels = 1; 246 bytesPerRow = width; 247 break; 248 } 249 case(24): 250 { 251 rgb24Image = new MemoryRGB24Image(width, height); 252 result = rgb24Image; 253 numChannels = 3; 254 bytesPerRow = width; 255 break; 256 } 257 } 258 setImage(result); 259 byte[][] buffer = new byte[numChannels][]; 260 for (int i = 0; i < numChannels; i++) 261 { 262 buffer[i] = new byte[bytesPerRow]; 263 } 264 for (int y = 0, destY = -getBoundsY1(); destY <= getBoundsY2(); y++, destY++) 265 { 266 if (rgb24Image != null) 267 { 268 for (int x = 0; x < width; x++) 269 { 270 buffer[RGBIndex.INDEX_BLUE][x] = in.readByte(); 271 buffer[RGBIndex.INDEX_GREEN][x] = in.readByte(); 272 buffer[RGBIndex.INDEX_RED][x] = in.readByte(); 273 } 274 rgb24Image.putByteSamples(RGBIndex.INDEX_RED, 0, destY, getBoundsWidth(), 1, buffer[0], getBoundsX1()); 275 rgb24Image.putByteSamples(RGBIndex.INDEX_GREEN, 0, destY, getBoundsWidth(), 1, buffer[1], getBoundsX1()); 276 rgb24Image.putByteSamples(RGBIndex.INDEX_BLUE, 0, destY, getBoundsWidth(), 1, buffer[2], getBoundsX1()); 277 } 278 else 279 if (paletted8Image != null) 280 { 281 in.readFully(buffer[0], 0, width); 282 paletted8Image.putByteSamples(0, 0, destY, getBoundsWidth(), 1, buffer[0], getBoundsX1()); 283 } 284 if (in.skipBytes(paddingBytes) != paddingBytes) 285 { 286 throw new InvalidFileStructureException("Could not skip " + 287 "byte after row " + y + "."); 288 } 289 setProgress(y, getBoundsY2() + 1); 290 } 291 return result; 292 } 293 294 private Palette readPalette() throws 295 InvalidFileStructureException, 296 java.io.IOException 297 { 298 Palette result = new Palette(256, 255); 299 for (int channel = 0; channel < 3; channel++) 300 { 301 int channelIndex = -1; 302 switch(channel) 303 { 304 case(0): 305 { 306 channelIndex = Palette.INDEX_RED; 307 break; 308 } 309 case(1): 310 { 311 channelIndex = Palette.INDEX_GREEN; 312 break; 313 } 314 case(2): 315 { 316 channelIndex = Palette.INDEX_BLUE; 317 break; 318 } 319 } 320 for (int i = 0; i < numColors; i++) 321 { 322 int value = in.readUnsignedByte(); 323 if (value == -1) 324 { 325 throw new InvalidFileStructureException("Unexpected end " + 326 "of file when reading Sun RAS palette."); 327 } 328 result.putSample(channelIndex, i, value); 329 } 330 } 331 return result; 332 } 333 334 private void save() throws 335 IOException, 336 UnsupportedTypeException, 337 WrongParameterException 338 { 339 PixelImage image = getImage(); 340 if (image == null || (!(image instanceof Paletted8Image))) 341 { 342 throw new UnsupportedTypeException("Must have non-null image that is a Paletted8Image."); 343 } 344 saveHeader(image); 345 if (image instanceof Paletted8Image) 346 { 347 saveData((Paletted8Image)image); 348 } 349 } 350 351 private void saveData(Paletted8Image image) throws IOException 352 { 353 byte[] row = new byte[getBoundsWidth()]; 354 for (int y1 = 0, y2 = getBoundsY1(); y1 < getBoundsHeight(); y1++, y2++) 355 { 356 image.getByteSamples(0, getBoundsX1(), y2, row.length, 1, row, 0); 357 out.write(row); 358 int num = paddingBytes; 359 while (num-- > 0) 360 { 361 out.write(0); 362 } 363 setProgress(y1, getBoundsHeight()); 364 } 365 } 366 367 private void saveHeader(PixelImage image) throws 368 IOException, 369 UnsupportedTypeException, 370 WrongParameterException 371 372 { 373 setBoundsIfNecessary(width, height); 374 checkBounds(width, height); 375 out.writeInt(RAS_MAGIC); 376 int width = getBoundsWidth(); 377 out.writeInt(width); 378 int height = getBoundsHeight(); 379 out.writeInt(height); 380 if (image instanceof BilevelImage) 381 { 382 depth = 1; 383 bytesPerRow = (width + 7) / 8; 384 } 385 else 386 if (image instanceof Gray8Image || 387 image instanceof Paletted8Image) 388 { 389 depth = 8; 390 bytesPerRow = width; 391 } 392 else 393 if (image instanceof RGB24Image) 394 { 395 bytesPerRow = width * 3; 396 depth = 24; 397 } 398 else 399 { 400 throw new UnsupportedTypeException("Cannot store image types " + 401 "other than bilevel, gray8, paletted8 and RGB24."); 402 } 403 out.writeInt(depth); 404 paddingBytes = (bytesPerRow % 2); 405 numColors = 1 << depth; 406 length = bytesPerRow * getBoundsHeight(); 407 out.writeInt(length); // length 408 out.writeInt(COMPRESSION_NONE); // type 409 mapType = 1; 410 mapLength = 0; 411 if (image instanceof Paletted8Image) 412 { 413 mapLength = 768; 414 } 415 out.writeInt(mapType); 416 out.writeInt(mapLength); 417 if (image instanceof Paletted8Image) 418 { 419 Paletted8Image pal = (Paletted8Image)image; 420 savePalette(pal.getPalette()); 421 } 422 } 423 424 private void savePalette(Palette palette) throws java.io.IOException 425 { 426 int numEntries = palette.getNumEntries(); 427 for (int channel = 0; channel < 3; channel++) 428 { 429 int channelIndex = -1; 430 switch(channel) 431 { 432 case(0): 433 { 434 channelIndex = Palette.INDEX_RED; 435 break; 436 } 437 case(1): 438 { 439 channelIndex = Palette.INDEX_GREEN; 440 break; 441 } 442 case(2): 443 { 444 channelIndex = Palette.INDEX_BLUE; 445 break; 446 } 447 } 448 for (int i = 0; i < 256; i++) 449 { 450 int value = 0; 451 if (i < numEntries) 452 { 453 value = palette.getSample(channelIndex, i); 454 } 455 out.write(value); 456 } 457 } 458 } 459 460 public String suggestFileExtension(PixelImage image) 461 { 462 return ".ras"; 463 } 464 }