001: /*
002: * $RCSfile: FPXImage.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.2 $
009: * $Date: 2005/05/12 18:24:31 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codecimpl.fpx;
013:
014: import java.awt.Point;
015: import java.awt.RenderingHints;
016: import java.awt.Transparency;
017: import java.awt.color.ColorSpace;
018: import java.awt.image.ColorModel;
019: import java.awt.image.ComponentColorModel;
020: import java.awt.image.DataBuffer;
021: import java.awt.image.DataBufferByte;
022: import java.awt.image.Raster;
023: import java.awt.image.SampleModel;
024: import java.awt.image.WritableRaster;
025: import java.io.ByteArrayInputStream;
026: import java.io.InputStream;
027: import java.io.IOException;
028: import java.util.Enumeration;
029: import java.util.Hashtable;
030: import com.sun.media.jai.codec.ImageCodec;
031: import com.sun.media.jai.codec.FPXDecodeParam;
032: import com.sun.media.jai.codec.SeekableStream;
033: import com.sun.media.jai.codecimpl.SimpleRenderedImage;
034: import com.sun.media.jai.codecimpl.util.RasterFactory;
035:
036: import com.sun.image.codec.jpeg.JPEGCodec;
037: import com.sun.image.codec.jpeg.JPEGDecodeParam;
038: import com.sun.image.codec.jpeg.JPEGEncodeParam;
039: import com.sun.image.codec.jpeg.JPEGImageDecoder;
040: import com.sun.media.jai.codecimpl.ImagingListenerProxy;
041: import com.sun.media.jai.codecimpl.util.ImagingException;
042:
043: public class FPXImage extends SimpleRenderedImage {
044:
045: private static final int SUBIMAGE_COLOR_SPACE_COLORLESS = 0;
046: private static final int SUBIMAGE_COLOR_SPACE_MONOCHROME = 0;
047: private static final int SUBIMAGE_COLOR_SPACE_PHOTOYCC = 0;
048: private static final int SUBIMAGE_COLOR_SPACE_NIFRGB = 0;
049: private static final String[] COLORSPACE_NAME = { "Colorless",
050: "Monochrome", "PhotoYCC", "NIF RGB" };
051:
052: StructuredStorage storage;
053:
054: int numResolutions;
055: int highestResWidth;
056: int highestResHeight;
057: float defaultDisplayHeight;
058: float defaultDisplayWidth;
059: int displayHeightWidthUnits;
060:
061: boolean[] subimageValid;
062: int[] subimageWidth;
063: int[] subimageHeight;
064:
065: int[][] subimageColor;
066:
067: // subimageNumericalFormat
068: int[] decimationMethod;
069: float[] decimationPrefilterWidth;
070: // subimage ICC profile
071:
072: int highestResolution = -1;
073:
074: int maxJPEGTableIndex;
075: byte[][] JPEGTable;
076:
077: // Values from "Subimage 0000 Header" stream
078: int numChannels;
079: int tileHeaderTableOffset;
080: int tileHeaderEntryLength;
081:
082: // int[] tileOffset;
083: // int[] tileSize;
084: // int[] compressionType;
085: // int[] compressionSubtype;
086:
087: // The "Subimage 0000 Header" stream
088: SeekableStream subimageHeaderStream;
089:
090: // The "Subimage 0000 Data" stream
091: SeekableStream subimageDataStream;
092:
093: int resolution;
094:
095: // Derived values
096: int tilesAcross;
097:
098: int[] bandOffsets = { 0, 1, 2 };
099:
100: private static final int[] RGBBits8 = { 8, 8, 8 };
101: private static final ComponentColorModel colorModelRGB8 = new ComponentColorModel(
102: ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), RGBBits8,
103: false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
104:
105: public FPXImage(SeekableStream stream, FPXDecodeParam param)
106: throws IOException {
107:
108: this .storage = new StructuredStorage(stream);
109:
110: readImageContents();
111: if (param == null) {
112: param = new FPXDecodeParam();
113: }
114: this .resolution = param.getResolution();
115: readResolution();
116:
117: bandOffsets = new int[numChannels];
118: for (int i = 0; i < numChannels; i++) {
119: bandOffsets[i] = i;
120: }
121:
122: this .minX = 0;
123: this .minY = 0;
124:
125: this .sampleModel = RasterFactory
126: .createPixelInterleavedSampleModel(
127: DataBuffer.TYPE_BYTE, tileWidth, tileHeight,
128: numChannels, numChannels * tileWidth,
129: bandOffsets);
130: this .colorModel = ImageCodec
131: .createComponentColorModel(sampleModel);
132: }
133:
134: private void readImageContents() throws IOException {
135: storage.changeDirectoryToRoot();
136: storage.changeDirectory("Data Object Store 000001");
137: SeekableStream imageContents = storage
138: .getStream("Image Contents");
139:
140: PropertySet icps = new PropertySet(imageContents);
141: this .numResolutions = (int) icps.getUI4(0x01000000);
142: this .highestResWidth = (int) icps.getUI4(0x01000002);
143: this .highestResHeight = (int) icps.getUI4(0x01000003);
144: // this.defaultDisplayHeight = icps.getR4(0x01000004);
145: // this.defaultDisplayWidth = icps.getR4(0x01000005);
146:
147: this .displayHeightWidthUnits = (int) icps
148: .getUI4(0x01000006, 0L);
149:
150: /*
151: System.out.println("\nImage Contents Property Set:\n");
152: System.out.println(" numResolutions = " + numResolutions);
153: System.out.println(" highestResWidth = " + highestResWidth);
154: System.out.println(" highestResHeight = " + highestResHeight);
155: System.out.println();
156: */
157:
158: this .subimageValid = new boolean[numResolutions];
159: this .subimageWidth = new int[numResolutions];
160: this .subimageHeight = new int[numResolutions];
161: this .subimageColor = new int[numResolutions][];
162: // subimageNumericalFormat
163: this .decimationMethod = new int[numResolutions];
164: this .decimationPrefilterWidth = new float[numResolutions];
165: // subimage ICC profile
166:
167: for (int i = 0; i < numResolutions; i++) {
168: int index = i << 16;
169: if (!icps.hasProperty(0x02000000 | index)) {
170: break;
171: }
172:
173: this .highestResolution = i;
174: subimageValid[i] = true;
175: subimageWidth[i] = (int) icps.getUI4(0x02000000 | index);
176: subimageHeight[i] = (int) icps.getUI4(0x02000001 | index);
177: byte[] subimageColorBlob = icps.getBlob(0x02000002 | index);
178: decimationMethod[i] = icps.getI4(0x02000004 | index);
179: // decimationPrefilterWidth[i] = icps.getR4(0x02000005 | index);
180:
181: int numSubImages = FPXUtils.getIntLE(subimageColorBlob, 0);
182: int numChannels = FPXUtils.getIntLE(subimageColorBlob, 4);
183:
184: // System.out.println(" subimageWidth[" + i + "] = " +
185: // subimageWidth[i]);
186: // System.out.println(" subimageHeight[" + i + "] = " +
187: // subimageHeight[i]);
188: // System.out.println(" subimageColor[" + i + "] = ");
189: // System.out.println(" numSubimages = " + numSubImages);
190:
191: subimageColor[i] = new int[numChannels];
192: for (int c = 0; c < numChannels; c++) {
193: int color = FPXUtils.getIntLE(subimageColorBlob,
194: 8 + 4 * c);
195: //
196: // Mask off the most significant bit as the FlashPix
197: // specification states that:
198: //
199: // "If the most significant bit of the color space subfield
200: // is set, then the image channel definitions are that of the
201: // color space, but the image is not calibrated. [...] the
202: // setting of the uncalibrated bit shall not imply any
203: // different processing path."
204: //
205: subimageColor[i][c] = (color & 0x7fffffff);
206: // System.out.println(" channel " + c + " color space " +
207: // (color >> 16) +
208: // " (" + COLORSPACE_NAME[color >> 16] +")");
209:
210: // System.out.println(" channel " + c + " color type " +
211: // (color & 0x7fff));
212: // if ((color & 0x8000) != 0) {
213: // System.out.println(" channel " + c + " has premultiplied opacity");
214: // }
215: }
216:
217: // System.out.println(" decimationMethod[" + i + "] = " +
218: // decimationMethod[i]);
219: // System.out.println();
220: }
221:
222: this .maxJPEGTableIndex = (int) icps.getUI4(0x03000002, -1L);
223: // System.out.println("maxJPEGTableIndex = " + maxJPEGTableIndex);
224: this .JPEGTable = new byte[maxJPEGTableIndex + 1][];
225: for (int i = 0; i <= maxJPEGTableIndex; i++) {
226: int index = i << 16;
227: if (icps.hasProperty(0x03000001 | index)) {
228: // System.out.println("Found a table at index " + i);
229: this .JPEGTable[i] = icps.getBlob(0x03000001 | index);
230: } else {
231: // System.out.println("No table at index " + i);
232: this .JPEGTable[i] = null;
233: }
234: }
235: }
236:
237: private void readResolution() throws IOException {
238: if (resolution == -1) {
239: resolution = this .highestResolution;
240: }
241:
242: // System.out.println("Reading resolution " + resolution);
243:
244: storage.changeDirectoryToRoot();
245: storage.changeDirectory("Data Object Store 000001");
246: storage.changeDirectory("Resolution 000" + resolution); // FIX
247:
248: this .subimageHeaderStream = storage
249: .getStream("Subimage 0000 Header");
250: subimageHeaderStream.skip(28);
251: int headerLength = subimageHeaderStream.readIntLE();
252: this .width = subimageHeaderStream.readIntLE();
253: this .height = subimageHeaderStream.readIntLE();
254: int numTiles = subimageHeaderStream.readIntLE();
255: this .tileWidth = subimageHeaderStream.readIntLE();
256: this .tileHeight = subimageHeaderStream.readIntLE();
257: this .numChannels = subimageHeaderStream.readIntLE();
258: this .tileHeaderTableOffset = subimageHeaderStream.readIntLE() + 28;
259: this .tileHeaderEntryLength = subimageHeaderStream.readIntLE();
260:
261: // System.out.println("\nResolution 000" + resolution + "\n");
262: // System.out.println("Subimage 0000 Header:\n");
263: // System.out.println(" headerLength = " + headerLength);
264: // System.out.println(" width = " + width);
265: // System.out.println(" height = " + height);
266: // System.out.println(" numTiles = " + numTiles);
267: // System.out.println(" tileWidth = " + tileWidth);
268: // System.out.println(" tileHeight = " + tileHeight);
269: // System.out.println(" numChannels = " + numChannels);
270: // System.out.println(" tileHeaderTableOffset = " +
271: // tileHeaderTableOffset);
272: // System.out.println(" tileHeaderEntryLength = " +
273: // tileHeaderEntryLength);
274:
275: this .subimageDataStream = storage
276: .getStream("Subimage 0000 Data");
277:
278: // Compute derived values
279: tilesAcross = (width + tileWidth - 1) / tileWidth;
280: }
281:
282: private int getTileOffset(int tileIndex) throws IOException {
283: // return tileOffset[tileIndex];
284:
285: subimageHeaderStream.seek(tileHeaderTableOffset + 16
286: * tileIndex);
287: return subimageHeaderStream.readIntLE() + 28;
288: }
289:
290: private int getTileSize(int tileIndex) throws IOException {
291: // return tileSize[tileIndex];
292:
293: subimageHeaderStream.seek(tileHeaderTableOffset + 16
294: * tileIndex + 4);
295: return subimageHeaderStream.readIntLE();
296: }
297:
298: private int getCompressionType(int tileIndex) throws IOException {
299: // return compressionType[tileIndex];
300:
301: subimageHeaderStream.seek(tileHeaderTableOffset + 16
302: * tileIndex + 8);
303: return subimageHeaderStream.readIntLE();
304: }
305:
306: private int getCompressionSubtype(int tileIndex) throws IOException {
307: // return compressionSubtype[tileIndex];
308:
309: subimageHeaderStream.seek(tileHeaderTableOffset + 16
310: * tileIndex + 12);
311: return subimageHeaderStream.readIntLE();
312: }
313:
314: private static final byte[] PhotoYCCToRGBLUT = { (byte) 0,
315: (byte) 1, (byte) 1, (byte) 2, (byte) 2, (byte) 3, (byte) 4,
316: (byte) 5, (byte) 6, (byte) 7, (byte) 8, (byte) 9,
317: (byte) 10, (byte) 11, (byte) 12, (byte) 13, (byte) 14,
318: (byte) 15, (byte) 16, (byte) 17, (byte) 18, (byte) 19,
319: (byte) 20, (byte) 22, (byte) 23, (byte) 24, (byte) 25,
320: (byte) 26, (byte) 28, (byte) 29, (byte) 30, (byte) 31,
321:
322: (byte) 33, (byte) 34, (byte) 35, (byte) 36, (byte) 38,
323: (byte) 39, (byte) 40, (byte) 41, (byte) 43, (byte) 44,
324: (byte) 45, (byte) 47, (byte) 48, (byte) 49, (byte) 51,
325: (byte) 52, (byte) 53, (byte) 55, (byte) 56, (byte) 57,
326: (byte) 59, (byte) 60, (byte) 61, (byte) 63, (byte) 64,
327: (byte) 65, (byte) 67, (byte) 68, (byte) 70, (byte) 71,
328: (byte) 72, (byte) 74,
329:
330: (byte) 75, (byte) 76, (byte) 78, (byte) 79, (byte) 81,
331: (byte) 82, (byte) 83, (byte) 85, (byte) 86, (byte) 88,
332: (byte) 89, (byte) 91, (byte) 92, (byte) 93, (byte) 95,
333: (byte) 96, (byte) 98, (byte) 99, (byte) 101, (byte) 102,
334: (byte) 103, (byte) 105, (byte) 106, (byte) 108, (byte) 109,
335: (byte) 111, (byte) 112, (byte) 113, (byte) 115, (byte) 116,
336: (byte) 118, (byte) 119,
337:
338: (byte) 121, (byte) 122, (byte) 123, (byte) 125, (byte) 126,
339: (byte) 128, (byte) 129, (byte) 130, (byte) 132, (byte) 133,
340: (byte) 134, (byte) 136, (byte) 137, (byte) 138, (byte) 140,
341: (byte) 141, (byte) 142, (byte) 144, (byte) 145, (byte) 146,
342: (byte) 148, (byte) 149, (byte) 150, (byte) 152, (byte) 153,
343: (byte) 154, (byte) 155, (byte) 157, (byte) 158, (byte) 159,
344: (byte) 160, (byte) 162,
345:
346: (byte) 163, (byte) 164, (byte) 165, (byte) 166, (byte) 168,
347: (byte) 169, (byte) 170, (byte) 171, (byte) 172, (byte) 174,
348: (byte) 175, (byte) 176, (byte) 177, (byte) 178, (byte) 179,
349: (byte) 180, (byte) 182, (byte) 183, (byte) 184, (byte) 185,
350: (byte) 186, (byte) 187, (byte) 188, (byte) 189, (byte) 190,
351: (byte) 191, (byte) 192, (byte) 194, (byte) 195, (byte) 196,
352: (byte) 197, (byte) 198,
353:
354: (byte) 199, (byte) 200, (byte) 201, (byte) 202, (byte) 203,
355: (byte) 204, (byte) 204, (byte) 205, (byte) 206, (byte) 207,
356: (byte) 208, (byte) 209, (byte) 210, (byte) 211, (byte) 212,
357: (byte) 213, (byte) 213, (byte) 214, (byte) 215, (byte) 216,
358: (byte) 217, (byte) 217, (byte) 218, (byte) 219, (byte) 220,
359: (byte) 221, (byte) 221, (byte) 222, (byte) 223, (byte) 223,
360: (byte) 224, (byte) 225,
361:
362: (byte) 225, (byte) 226, (byte) 227, (byte) 227, (byte) 228,
363: (byte) 229, (byte) 229, (byte) 230, (byte) 230, (byte) 231,
364: (byte) 231, (byte) 232, (byte) 233, (byte) 233, (byte) 234,
365: (byte) 234, (byte) 235, (byte) 235, (byte) 236, (byte) 236,
366: (byte) 236, (byte) 237, (byte) 237, (byte) 238, (byte) 238,
367: (byte) 238, (byte) 239, (byte) 239, (byte) 240, (byte) 240,
368: (byte) 240, (byte) 241,
369:
370: (byte) 241, (byte) 241, (byte) 242, (byte) 242, (byte) 242,
371: (byte) 242, (byte) 243, (byte) 243, (byte) 243, (byte) 244,
372: (byte) 244, (byte) 244, (byte) 244, (byte) 245, (byte) 245,
373: (byte) 245, (byte) 245, (byte) 245, (byte) 246, (byte) 246,
374: (byte) 246, (byte) 246, (byte) 246, (byte) 247, (byte) 247,
375: (byte) 247, (byte) 247, (byte) 247, (byte) 247, (byte) 248,
376: (byte) 248, (byte) 248,
377:
378: (byte) 248, (byte) 248, (byte) 248, (byte) 249, (byte) 249,
379: (byte) 249, (byte) 249, (byte) 249, (byte) 249, (byte) 249,
380: (byte) 249, (byte) 249, (byte) 250, (byte) 250, (byte) 250,
381: (byte) 250, (byte) 250, (byte) 250, (byte) 250, (byte) 250,
382: (byte) 250, (byte) 250, (byte) 251, (byte) 251, (byte) 251,
383: (byte) 251, (byte) 251, (byte) 251, (byte) 251, (byte) 251,
384: (byte) 251, (byte) 251,
385:
386: (byte) 251, (byte) 251, (byte) 251, (byte) 251, (byte) 252,
387: (byte) 252, (byte) 252, (byte) 252, (byte) 252, (byte) 252,
388: (byte) 252, (byte) 252, (byte) 252, (byte) 252, (byte) 252,
389: (byte) 252, (byte) 252, (byte) 252, (byte) 252, (byte) 252,
390: (byte) 252, (byte) 253, (byte) 253, (byte) 253, (byte) 253,
391: (byte) 253, (byte) 253, (byte) 253, (byte) 253, (byte) 253,
392: (byte) 253, (byte) 253,
393:
394: (byte) 253, (byte) 253, (byte) 253, (byte) 253, (byte) 253,
395: (byte) 253, (byte) 253, (byte) 254, (byte) 254, (byte) 254,
396: (byte) 254, (byte) 254, (byte) 254, (byte) 254, (byte) 254,
397: (byte) 254, (byte) 254, (byte) 254, (byte) 254, (byte) 254,
398: (byte) 254, (byte) 254, (byte) 255, (byte) 255, (byte) 255,
399: (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255,
400: (byte) 255, (byte) 255,
401:
402: (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255,
403: (byte) 255, (byte) 255, (byte) 255, (byte) 255 };
404:
405: private final byte PhotoYCCToNIFRed(float scaledY, float Cb,
406: float Cr) {
407: float red = scaledY + 1.8215F * Cr - 249.55F;
408: if (red < 0.0F) {
409: return (byte) 0;
410: } else if (red > 360.0F) {
411: return (byte) 255;
412: } else {
413: byte r = PhotoYCCToRGBLUT[(int) red];
414: return r;
415: }
416: }
417:
418: private final byte PhotoYCCToNIFGreen(float scaledY, float Cb,
419: float Cr) {
420: float green = scaledY - .43031F * Cb - .9271F * Cr + 194.14F;
421: if (green < 0.0F) {
422: return (byte) 0;
423: } else if (green > 360.0F) {
424: return (byte) 255;
425: } else {
426: byte g = PhotoYCCToRGBLUT[(int) green];
427: return g;
428: }
429: }
430:
431: private final byte PhotoYCCToNIFBlue(float scaledY, float Cb,
432: float Cr) {
433: float blue = scaledY + 2.2179F * Cb - 345.99F;
434: if (blue < 0.0F) {
435: return (byte) 0;
436: } else if (blue > 360.0F) {
437: return (byte) 255;
438: } else {
439: byte b = PhotoYCCToRGBLUT[(int) blue];
440: return b;
441: }
442: }
443:
444: private final byte YCCToNIFRed(float Y, float Cb, float Cr) {
445: float red = Y + 1.402F * Cr - (255.0F * .701F);
446: if (red < 0.0F) {
447: return (byte) 0;
448: } else if (red > 255.0F) {
449: return (byte) 255;
450: } else {
451: return (byte) red;
452: }
453: }
454:
455: private final byte YCCToNIFGreen(float Y, float Cb, float Cr) {
456: float green = Y - .34414F * Cb - .71414F * Cr
457: + (255.0F * .52914F);
458: if (green < 0.0F) {
459: return (byte) 0;
460: } else if (green > 255.0F) {
461: return (byte) 255;
462: } else {
463: return (byte) green;
464: }
465: }
466:
467: private final byte YCCToNIFBlue(float Y, float Cb, float Cr) {
468: float blue = Y + 1.772F * Cb - (255.0F * .886F);
469: if (blue < 0.0F) {
470: return (byte) 0;
471: } else if (blue > 255.0F) {
472: return (byte) 255;
473: } else {
474: return (byte) blue;
475: }
476: }
477:
478: private Raster getUncompressedTile(int tileX, int tileY)
479: throws IOException {
480: int tx = tileXToX(tileX);
481: int ty = tileYToY(tileY);
482: Raster ras = RasterFactory.createInterleavedRaster(
483: DataBuffer.TYPE_BYTE, tileWidth, tileHeight,
484: numChannels * tileWidth, numChannels, bandOffsets,
485: new Point(tx, ty));
486: // System.out.println("Uncompressed tile.");
487:
488: DataBufferByte dataBuffer = (DataBufferByte) ras
489: .getDataBuffer();
490: byte[] data = dataBuffer.getData();
491:
492: int tileIndex = tileY * tilesAcross + tileX;
493: subimageDataStream.seek(getTileOffset(tileIndex));
494: subimageDataStream.readFully(data, 0, numChannels * tileWidth
495: * tileHeight);
496:
497: // Color convert if subimage is in PhotoYCC format
498: if (subimageColor[resolution][0] >> 16 == 2) {
499: int size = tileWidth * tileHeight;
500: for (int i = 0; i < size; i++) {
501: float Y = data[3 * i] & 0xff;
502: float Cb = data[3 * i + 1] & 0xff;
503: float Cr = data[3 * i + 2] & 0xff;
504:
505: float scaledY = Y * 1.3584F;
506: byte red = PhotoYCCToNIFRed(scaledY, Cb, Cr);
507: byte green = PhotoYCCToNIFGreen(scaledY, Cb, Cr);
508: byte blue = PhotoYCCToNIFBlue(scaledY, Cb, Cr);
509:
510: data[3 * i] = red;
511: data[3 * i + 1] = green;
512: data[3 * i + 2] = blue;
513: }
514: }
515:
516: return ras;
517: }
518:
519: private Raster getSingleColorCompressedTile(int tileX, int tileY)
520: throws IOException {
521: // System.out.println("Single color compressed tile.");
522:
523: int tx = tileXToX(tileX);
524: int ty = tileYToY(tileY);
525: Raster ras = RasterFactory.createInterleavedRaster(
526: DataBuffer.TYPE_BYTE, tileWidth, tileHeight,
527: numChannels * tileWidth, numChannels, bandOffsets,
528: new Point(tx, ty));
529:
530: int subimageColorType = subimageColor[resolution][0] >> 16;
531:
532: DataBufferByte dataBuffer = (DataBufferByte) ras
533: .getDataBuffer();
534: byte[] data = dataBuffer.getData();
535:
536: int tileIndex = tileY * tilesAcross + tileX;
537: int color = getCompressionSubtype(tileIndex);
538: byte c0 = (byte) ((color >> 0) & 0xff);
539: byte c1 = (byte) ((color >> 8) & 0xff);
540: byte c2 = (byte) ((color >> 16) & 0xff);
541: byte alpha = (byte) ((color >> 24) & 0xff);
542:
543: byte red, green, blue;
544:
545: // Color convert if subimage is in PhotoYCC format
546: if (subimageColor[resolution][0] >> 16 == 2) {
547: float Y = c0 & 0xff;
548: float Cb = c1 & 0xff;
549: float Cr = c2 & 0xff;
550:
551: float scaledY = Y * 1.3584F;
552: red = PhotoYCCToNIFRed(scaledY, Cb, Cr);
553: green = PhotoYCCToNIFGreen(scaledY, Cb, Cr);
554: blue = PhotoYCCToNIFBlue(scaledY, Cb, Cr);
555: } else {
556: red = c0;
557: green = c1;
558: blue = c2;
559: }
560:
561: int index = 0;
562: int pixels = tileWidth * tileHeight;
563:
564: if (numChannels == 1) {
565: } else if (numChannels == 2) {
566: } else if (numChannels == 3) {
567: for (int i = 0; i < pixels; i++) {
568: data[index + 0] = red;
569: data[index + 1] = green;
570: data[index + 2] = blue;
571:
572: index += 3;
573: }
574: } else if (numChannels == 4) {
575: for (int i = 0; i < pixels; i++) {
576: data[index + 0] = red;
577: data[index + 1] = green;
578: data[index + 2] = blue;
579: data[index + 3] = alpha;
580:
581: index += 4;
582: }
583: }
584:
585: return ras;
586: }
587:
588: private Raster getJPEGCompressedTile(int tileX, int tileY)
589: throws IOException {
590: // System.out.println("JPEG compressed tile.");
591:
592: int tileIndex = tileY * tilesAcross + tileX;
593:
594: int tx = tileXToX(tileX);
595: int ty = tileYToY(tileY);
596:
597: int subtype = getCompressionSubtype(tileIndex);
598: int interleave = (subtype >> 0) & 0xff;
599: int chroma = (subtype >> 8) & 0xff;
600: int conversion = (subtype >> 16) & 0xff;
601: int table = (subtype >> 24) & 0xff;
602:
603: JPEGImageDecoder dec;
604: JPEGDecodeParam param = null;
605:
606: if (table != 0) {
607: InputStream tableStream = new ByteArrayInputStream(
608: JPEGTable[table]);
609: dec = JPEGCodec.createJPEGDecoder(tableStream);
610: Raster junk = dec.decodeAsRaster();
611: param = dec.getJPEGDecodeParam();
612: }
613:
614: subimageDataStream.seek(getTileOffset(tileIndex));
615: if (param != null) {
616: dec = JPEGCodec
617: .createJPEGDecoder(subimageDataStream, param);
618: } else {
619: dec = JPEGCodec.createJPEGDecoder(subimageDataStream);
620: }
621: Raster ras = dec.decodeAsRaster().createTranslatedChild(tx, ty);
622:
623: DataBufferByte dataBuffer = (DataBufferByte) ras
624: .getDataBuffer();
625: byte[] data = dataBuffer.getData();
626:
627: int subimageColorType = subimageColor[resolution][0] >> 16;
628:
629: int size = tileWidth * tileHeight;
630: if ((conversion == 0) && (subimageColorType == 2)) {
631: // System.out.println("Converting PhotoYCC to NIFRGB");
632: int offset = 0;
633: for (int i = 0; i < size; i++) {
634: float Y = data[offset] & 0xff;
635: float Cb = data[offset + 1] & 0xff;
636: float Cr = data[offset + 2] & 0xff;
637:
638: float scaledY = Y * 1.3584F;
639: byte red = PhotoYCCToNIFRed(scaledY, Cb, Cr);
640: byte green = PhotoYCCToNIFGreen(scaledY, Cb, Cr);
641: byte blue = PhotoYCCToNIFBlue(scaledY, Cb, Cr);
642:
643: data[offset] = red;
644: data[offset + 1] = green;
645: data[offset + 2] = blue;
646:
647: offset += numChannels;
648: }
649: } else if ((conversion == 1) && (subimageColorType == 3)) {
650: // System.out.println("Converting YCC to NIFRGB");
651: int offset = 0;
652: for (int i = 0; i < size; i++) {
653: float Y = data[offset] & 0xff;
654: float Cb = data[offset + 1] & 0xff;
655: float Cr = data[offset + 2] & 0xff;
656:
657: byte red = YCCToNIFRed(Y, Cb, Cr);
658: byte green = YCCToNIFGreen(Y, Cb, Cr);
659: byte blue = YCCToNIFBlue(Y, Cb, Cr);
660:
661: data[offset] = red;
662: data[offset + 1] = green;
663: data[offset + 2] = blue;
664:
665: offset += numChannels;
666: }
667: }
668:
669: // Perform special inversion step when output space is
670: // NIF RGB (subimageColorType == 3) with premultiplied opacity
671: // (numChannels == 4).
672: if ((conversion == 1) && (subimageColorType == 3)
673: && (numChannels == 4)) {
674: // System.out.println("Flipping NIFRGB");
675:
676: int offset = 0;
677: for (int i = 0; i < size; i++) {
678: data[offset + 0] = (byte) (255 - data[offset + 0]);
679: data[offset + 1] = (byte) (255 - data[offset + 1]);
680: data[offset + 2] = (byte) (255 - data[offset + 2]);
681:
682: offset += 4;
683: }
684: }
685:
686: return ras;
687: }
688:
689: public synchronized Raster getTile(int tileX, int tileY) {
690: int tileIndex = tileY * tilesAcross + tileX;
691:
692: try {
693: int ctype = getCompressionType(tileIndex);
694: if (ctype == 0) {
695: return getUncompressedTile(tileX, tileY);
696: } else if (ctype == 1) {
697: return getSingleColorCompressedTile(tileX, tileY);
698: } else if (ctype == 2) {
699: return getJPEGCompressedTile(tileX, tileY);
700: }
701: return null;
702: } catch (IOException e) {
703: ImagingListenerProxy
704: .errorOccurred(JaiI18N.getString("FPXImage0"), e,
705: ImageCodec.class, false);
706: // e.printStackTrace();
707: return null;
708: }
709: }
710:
711: Hashtable properties = null;
712:
713: private void addLPSTRProperty(String name, PropertySet ps, int id) {
714: String s = ps.getLPSTR(id);
715: if (s != null) {
716: properties.put(name.toLowerCase(), s);
717: }
718: }
719:
720: private void addLPWSTRProperty(String name, PropertySet ps, int id) {
721: String s = ps.getLPWSTR(id);
722: if (s != null) {
723: properties.put(name.toLowerCase(), s);
724: }
725: }
726:
727: private void addUI4Property(String name, PropertySet ps, int id) {
728: if (ps.hasProperty(id)) {
729: long i = ps.getUI4(id);
730: properties.put(name.toLowerCase(), new Integer((int) i));
731: }
732: }
733:
734: private void getSummaryInformation() {
735: SeekableStream summaryInformation = null;
736: PropertySet sips = null;
737: try {
738: storage.changeDirectoryToRoot();
739: summaryInformation = storage
740: .getStream("SummaryInformation");
741: sips = new PropertySet(summaryInformation);
742: } catch (IOException e) {
743: ImagingListenerProxy
744: .errorOccurred(JaiI18N.getString("FPXImage1"), e,
745: ImageCodec.class, false);
746: // e.printStackTrace();
747: return;
748: }
749:
750: addLPSTRProperty("title", sips, 0x000000002);
751: addLPSTRProperty("subject", sips, 0x000000003);
752: addLPSTRProperty("author", sips, 0x000000004);
753: addLPSTRProperty("keywords", sips, 0x000000005);
754: addLPSTRProperty("comments", sips, 0x000000006);
755: addLPSTRProperty("template", sips, 0x000000007);
756: addLPSTRProperty("last saved by", sips, 0x000000008);
757: addLPSTRProperty("revision number", sips, 0x000000009);
758: }
759:
760: private void getImageInfo() {
761: SeekableStream imageInfo = null;
762: PropertySet iips = null;
763: try {
764: storage.changeDirectoryToRoot();
765: imageInfo = storage.getStream("Image Info");
766: if (imageInfo == null) {
767: return;
768: }
769: iips = new PropertySet(imageInfo);
770: } catch (IOException e) {
771: ImagingListenerProxy
772: .errorOccurred(JaiI18N.getString("FPXImage2"), e,
773: ImageCodec.class, false);
774: // e.printStackTrace();
775: return;
776: }
777:
778: addUI4Property("file source", iips, 0x21000000);
779: addUI4Property("scene type", iips, 0x21000001);
780: // creation path vector
781: addLPWSTRProperty("software name/manufacturer/release", iips,
782: 0x21000003);
783: addLPWSTRProperty("user defined id", iips, 0x21000004);
784:
785: addLPWSTRProperty("copyright message", iips, 0x22000000);
786: addLPWSTRProperty("legal broker for the original image", iips,
787: 0x22000001);
788: addLPWSTRProperty("legal broker for the digital image", iips,
789: 0x22000002);
790: addLPWSTRProperty("authorship", iips, 0x22000003);
791: addLPWSTRProperty("intellectual property notes", iips,
792: 0x22000004);
793: }
794:
795: private synchronized void getProperties() {
796: if (properties != null) {
797: return;
798: }
799: properties = new Hashtable();
800:
801: getSummaryInformation();
802: getImageInfo();
803:
804: // Ad hoc properties
805: properties
806: .put("max_resolution", new Integer(highestResolution));
807: }
808:
809: public String[] getPropertyNames() {
810: getProperties();
811:
812: int len = properties.size();
813: String[] names = new String[len];
814: Enumeration enumeration = properties.keys();
815:
816: int count = 0;
817: while (enumeration.hasMoreElements()) {
818: names[count++] = (String) enumeration.nextElement();
819: }
820:
821: return names;
822: }
823:
824: public Object getProperty(String name) {
825: getProperties();
826: return properties.get(name.toLowerCase());
827: }
828: }
|