0001: /*
0002: * $RCSfile: IIPResolutionOpImage.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.1 $
0009: * $Date: 2005/02/11 04:56:28 $
0010: * $State: Exp $
0011: */
0012: package com.sun.media.jai.opimage;
0013:
0014: import java.awt.Point;
0015: import java.awt.Rectangle;
0016: import java.awt.RenderingHints;
0017: import java.awt.Transparency;
0018: import java.awt.color.ColorSpace;
0019: import java.awt.geom.AffineTransform;
0020: import java.awt.geom.Rectangle2D;
0021: import java.awt.image.ColorModel;
0022: import java.awt.image.ComponentColorModel;
0023: import java.awt.image.DataBuffer;
0024: import java.awt.image.DataBufferByte;
0025: import java.awt.image.Raster;
0026: import java.awt.image.RenderedImage;
0027: import java.awt.image.SampleModel;
0028: import java.awt.image.WritableRaster;
0029: import java.io.InputStream;
0030: import java.io.ByteArrayInputStream;
0031: import java.net.URL;
0032: import java.util.Vector;
0033: import javax.media.jai.ImageLayout;
0034: import javax.media.jai.JAI;
0035: import javax.media.jai.OpImage;
0036: import javax.media.jai.RasterFactory;
0037: import javax.media.jai.util.ImagingException;
0038: import javax.media.jai.util.ImagingListener;
0039: import java.util.Map;
0040: import com.sun.image.codec.jpeg.JPEGCodec;
0041: import com.sun.image.codec.jpeg.JPEGDecodeParam;
0042: import com.sun.image.codec.jpeg.JPEGImageDecoder;
0043: import com.sun.media.jai.util.ImageUtil;
0044:
0045: /**
0046: * An OpImage class to generate an image from an IIP connection. A single
0047: * resolution level of the remote IIP image is retrieved.
0048: *
0049: * @see javax.media.jai.operator.IIPDescriptor
0050: * @since 1.0
0051: */
0052: public class IIPResolutionOpImage extends OpImage {
0053: // Tile dimension are fixed at 64x64.
0054: private static final int TILE_SIZE = 64;
0055:
0056: // Default dimensions in tiles of block of tiles to retrieve.
0057: private static final int TILE_BLOCK_WIDTH = 8;
0058: private static final int TILE_BLOCK_HEIGHT = 2;
0059:
0060: // Significant delimiters in responses.
0061: private static final char BLANK = ' ';
0062: private static final char COLON = ':';
0063: private static final char SLASH = '/';
0064: private static final char CR = 0x0d;
0065: private static final char LF = 0x0a;
0066:
0067: // Colorspace information
0068: private static final int CS_COLORLESS = 0x0;
0069: private static final int CS_MONOCHROME = 0x1;
0070: private static final int CS_PHOTOYCC = 0x2;
0071: private static final int CS_NIFRGB = 0x3;
0072: private static final int CS_PLANE_ALPHA = 0x7ffe;
0073:
0074: // Compression types
0075: private static final int TILE_UNCOMPRESSED = 0x0;
0076: private static final int TILE_SINGLE_COLOR = 0x1;
0077: private static final int TILE_JPEG = 0x2;
0078: private static final int TILE_INVALID = 0xffffffff;
0079:
0080: // cache the ImagingListener
0081: private static ImagingListener listener = JAI.getDefaultInstance()
0082: .getImagingListener();
0083:
0084: /* The base URL string of the IIP server and image. */
0085: private String URLString;
0086:
0087: /* The desired resolution in IIP order: 0 is lowest resolution. */
0088: private int resolution;
0089:
0090: /* The desired sub-image. */
0091: private int subImage;
0092:
0093: /* The colorspace type. */
0094: private int colorSpaceType;
0095:
0096: /* Flag indicating whether the image has an opacity channel. */
0097: private boolean hasAlpha;
0098:
0099: /* Flag indicating whether the opacity channel is premultiplied. */
0100: private boolean isAlphaPremultilpied;
0101:
0102: /* The minimum tile in the X direction. */
0103: private int minTileX;
0104:
0105: /* The minimum tile in the Y direction. */
0106: private int minTileY;
0107:
0108: /* The number of tiles in the X direction. */
0109: private int numXTiles;
0110:
0111: /* The JPEGDecodeParam cache */
0112: private JPEGDecodeParam[] decodeParamCache = new JPEGDecodeParam[255];
0113:
0114: /* Property initialization flag. */
0115: private boolean arePropertiesInitialized = false;
0116:
0117: /* Tile block dimensions eventually used. */
0118: private int tileBlockWidth = TILE_BLOCK_WIDTH;
0119: private int tileBlockHeight = TILE_BLOCK_HEIGHT;
0120:
0121: /** cache to extract the ImagingListener. */
0122: private RenderingHints renderHints;
0123:
0124: /*
0125: * Convert YCbCr to NIF RGB using the appropriate algorithm.
0126: */
0127: private static final void YCbCrToNIFRGB(Raster raster) {
0128: byte[] data = ((DataBufferByte) raster.getDataBuffer())
0129: .getData();
0130:
0131: int offset = 0;
0132: int length = data.length;
0133: int MASK1 = 0x000000ff;
0134: int MASK2 = 0x0000ff00;
0135: if (raster.getSampleModel().getNumBands() == 3) {
0136: while (offset < length) {
0137: float Y = data[offset] & 0xff;
0138: float Cb = data[offset + 1] & 0xff;
0139: float Cr = data[offset + 2] & 0xff;
0140:
0141: int R = (int) (Y + 1.40200F * Cr - 178.255F);
0142: int G = (int) (Y - 0.34414F * Cb - 0.71414F * Cr + 135.4307F);
0143: int B = (int) (Y + 1.77200F * Cb - 225.43F);
0144:
0145: int imask = (R >> 5) & 0x18;
0146: data[offset++] = (byte) (((R & (MASK1 >> imask)) | (MASK2 >> imask)) & 0xff);
0147:
0148: imask = (G >> 5) & 0x18;
0149: data[offset++] = (byte) (((G & (MASK1 >> imask)) | (MASK2 >> imask)) & 0xff);
0150:
0151: imask = (B >> 5) & 0x18;
0152: data[offset++] = (byte) (((B & (MASK1 >> imask)) | (MASK2 >> imask)) & 0xff);
0153: }
0154: } else { // numBands == 4 (premultiplied NIFRGB with opacity)
0155: while (offset < length) {
0156: float Y = data[offset] & 0xff;
0157: float Cb = data[offset + 1] & 0xff;
0158: float Cr = data[offset + 2] & 0xff;
0159:
0160: int R = (int) (-Y - 1.40200F * Cr - 433.255F);
0161: int G = (int) (-Y + 0.34414F * Cb + 0.71414F * Cr + 119.5693F);
0162: int B = (int) (-Y - 1.77200F * Cb - 480.43F);
0163:
0164: int imask = (R >> 5) & 0x18;
0165: data[offset++] = (byte) (((R & (MASK1 >> imask)) | (MASK2 >> imask)) & 0xff);
0166:
0167: imask = (G >> 5) & 0x18;
0168: data[offset++] = (byte) (((G & (MASK1 >> imask)) | (MASK2 >> imask)) & 0xff);
0169:
0170: imask = (B >> 5) & 0x18;
0171: data[offset++] = (byte) (((B & (MASK1 >> imask)) | (MASK2 >> imask)) & 0xff);
0172:
0173: offset++; // skip the opacity
0174: }
0175: }
0176: }
0177:
0178: /*
0179: * Post one or more IIP commands using the HTTP protocol and return a
0180: * stream from which the response(s) may be read. The "commands" parameter
0181: * may be null.
0182: */
0183: private static InputStream postCommands(String URLSpec,
0184: String[] commands) {
0185: // Construct the initial command by appending OBJ=IIP,1.0
0186: StringBuffer spec = new StringBuffer(URLSpec + "&OBJ=iip,1.0");
0187:
0188: if (commands != null) {
0189: // Append the commands to the string.
0190: for (int i = 0; i < commands.length; i++) {
0191: spec.append("&" + commands[i]);
0192: }
0193: }
0194:
0195: // Construct the URL and open a stream to read from it.
0196: InputStream stream = null;
0197: try {
0198: URL url = new URL(spec.toString());
0199: stream = url.openStream();
0200: } catch (Exception e) {
0201: String message = JaiI18N.getString("IIPResolution4")
0202: + spec.toString();
0203: listener.errorOccurred(message, new ImagingException(
0204: message, e), IIPResolutionOpImage.class, false);
0205: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0206: }
0207:
0208: return stream;
0209: }
0210:
0211: /*
0212: * Retrieve the label of the IIP server response. The label is defined
0213: * to be the bytes in the stream up to the first SLASH or COLON. The
0214: * returned value will be null if an EOS is reached before any characters
0215: * which are neither a SLASH or a COLON. If the returned String is non-null
0216: * it will be lower case.
0217: */
0218: private static String getLabel(InputStream stream) {
0219: boolean charsAppended = false;
0220: StringBuffer buf = new StringBuffer(16);
0221: try {
0222: int i;
0223: while ((i = stream.read()) != -1) {
0224: char c = (char) (0x000000ff & i);
0225: if (c == SLASH || c == COLON) {
0226: break;
0227: }
0228: buf.append(c);
0229: charsAppended = true;
0230: }
0231: } catch (Exception e) {
0232: String message = JaiI18N.getString("IIPResolution5");
0233: listener.errorOccurred(message, new ImagingException(
0234: message, e), IIPResolutionOpImage.class, false);
0235: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0236: }
0237:
0238: return charsAppended ? buf.toString().toLowerCase() : null;
0239: }
0240:
0241: /*
0242: * Retrieve the length of the data stream. This length is assumed to
0243: * be the an INT derived from the portion of the stream before the
0244: * first COLON.
0245: */
0246: private static int getLength(InputStream stream) {
0247: return Integer.valueOf(getLabel(stream)).intValue();
0248: }
0249:
0250: /*
0251: * Throw or print a RuntimeException in response to an error returned by
0252: * the IIP server. If no error was returned do nothing. Also grab and
0253: * discard the response to the "OBJ=iip" command which is always present.
0254: */
0255: private static InputStream checkError(String label,
0256: InputStream stream, boolean throwException) {
0257: if (label.equals("error")) {
0258: int length = Integer.valueOf(getLabel(stream)).intValue();
0259: byte[] b = new byte[length];
0260: try {
0261: stream.read(b);
0262: } catch (Exception e) {
0263: String message = JaiI18N.getString("IIPResolution6");
0264: listener.errorOccurred(message, new ImagingException(
0265: message, e), IIPResolutionOpImage.class, false);
0266: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0267: }
0268: String msg = new String(b);
0269: if (throwException) {
0270: throwIIPException(msg);
0271: } else {
0272: printIIPException(msg);
0273: }
0274: } else if (label.startsWith("iip")) {
0275: // Ignore this response.
0276: String iipObjectResponse = getDataAsString(stream, false);
0277: }
0278:
0279: return stream;
0280: }
0281:
0282: /*
0283: * Returns the next segment of the stream until EOS or CRLF as a byte[].
0284: * The length of the data must be available as an INT in the stream before
0285: * the COLON.
0286: */
0287: private static byte[] getDataAsByteArray(InputStream stream) {
0288: int length = getLength(stream);
0289: byte[] b = new byte[length];
0290:
0291: try {
0292: stream.read(b);
0293: stream.read(); // CR
0294: stream.read(); // LF
0295: } catch (Exception e) {
0296: String message = JaiI18N.getString("IIPResolution7");
0297: listener.errorOccurred(message, new ImagingException(
0298: message, e), IIPResolutionOpImage.class, false);
0299: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0300: }
0301:
0302: return b;
0303: }
0304:
0305: /*
0306: * Returns the next segment of the stream until EOS or CRLF as a String.
0307: */
0308: private static String getDataAsString(InputStream stream,
0309: boolean hasLength) {
0310: String str = null;
0311: if (hasLength) {
0312: try {
0313: int length = getLength(stream);
0314: byte[] b = new byte[length];
0315: stream.read(b);
0316: stream.read(); // CR
0317: stream.read(); // LF
0318: str = new String(b);
0319: } catch (Exception e) {
0320: String message = JaiI18N.getString("IIPResolution7");
0321: listener.errorOccurred(message, new ImagingException(
0322: message, e), IIPResolutionOpImage.class, false);
0323: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0324: }
0325: } else {
0326: StringBuffer buf = new StringBuffer(16);
0327: try {
0328: int i;
0329: while ((i = stream.read()) != -1) {
0330: char c = (char) (0x000000ff & i);
0331: if (c == CR) { // if last byte was CR
0332: stream.read(); // LF
0333: break;
0334: }
0335: buf.append(c);
0336: }
0337: str = buf.toString();
0338: } catch (Exception e) {
0339: String message = JaiI18N.getString("IIPResolution7");
0340: listener.errorOccurred(message, new ImagingException(
0341: message, e), IIPResolutionOpImage.class, false);
0342: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0343: }
0344: }
0345:
0346: return str;
0347: }
0348:
0349: /*
0350: * Flush the next segment of the stream until EOS or CRLF.
0351: */
0352: private static void flushData(InputStream stream, boolean hasLength) {
0353: if (hasLength) {
0354: try {
0355: int length = getLength(stream);
0356: long numSkipped = stream.skip(length);
0357: if (numSkipped == length) {
0358: stream.read(); // CR
0359: stream.read(); // LF
0360: }
0361: } catch (Exception e) {
0362: String message = JaiI18N.getString("IIPResolution8");
0363: listener.errorOccurred(message, new ImagingException(
0364: message, e), IIPResolutionOpImage.class, false);
0365: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0366: }
0367: } else {
0368: try {
0369: int i;
0370: while ((i = stream.read()) != -1) {
0371: if ((char) (0x000000ff & i) == CR) { // if last byte was CR
0372: stream.read(); // LF
0373: break;
0374: }
0375: }
0376: } catch (Exception e) {
0377: String message = JaiI18N.getString("IIPResolution8");
0378: listener.errorOccurred(message, new ImagingException(
0379: message, e), IIPResolutionOpImage.class, false);
0380: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0381: }
0382: }
0383: }
0384:
0385: /*
0386: * Convert a string containing BLANK-seprated INTs into an int[].
0387: */
0388: private static int[] stringToIntArray(String s) {
0389: // Parse string into a Vector.
0390: Vector v = new Vector();
0391: int lastBlank = 0;
0392: int nextBlank = s.indexOf(BLANK, 0);
0393: do {
0394: v.add(Integer.valueOf(s.substring(lastBlank, nextBlank)));
0395: lastBlank = nextBlank + 1;
0396: nextBlank = s.indexOf(BLANK, lastBlank);
0397: } while (nextBlank != -1);
0398: v.add(Integer.valueOf(s.substring(lastBlank)));
0399:
0400: // Convert the Vector to a int[].
0401: int length = v.size();
0402: int[] intArray = new int[length];
0403: for (int i = 0; i < length; i++) {
0404: intArray[i] = ((Integer) v.get(i)).intValue();
0405: }
0406:
0407: return intArray;
0408: }
0409:
0410: /*
0411: * Convert a string containing BLANK-seprated FLOATs into a float[].
0412: */
0413: private static float[] stringToFloatArray(String s) {
0414: // Parse string into a Vector.
0415: Vector v = new Vector();
0416: int lastBlank = 0;
0417: int nextBlank = s.indexOf(BLANK, 0);
0418: do {
0419: v.add(Float.valueOf(s.substring(lastBlank, nextBlank)));
0420: lastBlank = nextBlank + 1;
0421: nextBlank = s.indexOf(BLANK, lastBlank);
0422: } while (nextBlank != -1);
0423: v.add(Float.valueOf(s.substring(lastBlank)));
0424:
0425: // Convert the Vector to a float[].
0426: int length = v.size();
0427: float[] floatArray = new float[length];
0428: for (int i = 0; i < length; i++) {
0429: floatArray[i] = ((Float) v.get(i)).floatValue();
0430: }
0431:
0432: return floatArray;
0433: }
0434:
0435: /*
0436: * Format the argument into a generic IIP error message.
0437: */
0438: private static String formatIIPErrorMessage(String msg) {
0439: return new String(JaiI18N.getString("IIPResolutionOpImage0")
0440: + " " + msg);
0441: }
0442:
0443: /*
0444: * Throw a RuntimeException with the indicated message.
0445: */
0446: private static void throwIIPException(String msg) {
0447: throw new RuntimeException(formatIIPErrorMessage(msg));
0448: }
0449:
0450: /*
0451: * Print the supplied message to the standard error stream.
0452: */
0453: private static void printIIPException(String msg) {
0454: System.err.println(formatIIPErrorMessage(msg));
0455: }
0456:
0457: /*
0458: * Close the supplied stream ignoring any exceptions.
0459: */
0460: private static void closeStream(InputStream stream) {
0461: try {
0462: stream.close();
0463: } catch (Exception e) {
0464: // Ignore
0465: }
0466: }
0467:
0468: /*
0469: * Derive the image layout (tile grid, image bounds, ColorModel, and
0470: * SampleModel) by querying the IIP server.
0471: */
0472: private static ImageLayout layoutHelper(String URLSpec, int level,
0473: int subImage) {
0474: // Create an ImageLayout by construction or cloning.
0475: ImageLayout il = new ImageLayout();
0476:
0477: // Set the tile offsets to (0,0).
0478: il.setTileGridXOffset(0);
0479: il.setTileGridYOffset(0);
0480:
0481: // Set the tile dimensions.
0482: il.setTileWidth(TILE_SIZE);
0483: il.setTileHeight(TILE_SIZE);
0484:
0485: // Set the image origin to (0,0).
0486: il.setMinX(0);
0487: il.setMinY(0);
0488:
0489: // Retrieve the number of resolutions available and the maximum
0490: // width and height (the dimensions of resolution numRes - 1).
0491: int maxWidth = -1;
0492: int maxHeight = -1;
0493: int numRes = -1;
0494: int resolution = -1;
0495: String[] cmd = new String[] { "OBJ=Max-size",
0496: "OBJ=Resolution-number" };
0497: InputStream stream = postCommands(URLSpec, cmd);
0498: String label = null;
0499: while ((label = getLabel(stream)) != null) {
0500: if (label.equals("max-size")) {
0501: String data = getDataAsString(stream, false);
0502: int[] wh = stringToIntArray(data);
0503: maxWidth = wh[0];
0504: maxHeight = wh[1];
0505: } else if (label.equals("resolution-number")) {
0506: String data = getDataAsString(stream, false);
0507: numRes = Integer.valueOf(data).intValue();
0508: if (level < 0) {
0509: resolution = 0;
0510: } else if (level >= numRes) {
0511: resolution = numRes - 1;
0512: } else {
0513: resolution = level;
0514: }
0515: } else {
0516: checkError(label, stream, true);
0517: }
0518: }
0519: closeStream(stream);
0520:
0521: // Derive the width and height for this resolution level.
0522: int w = maxWidth;
0523: int h = maxHeight;
0524: for (int i = numRes - 1; i > resolution; i--) {
0525: w = (w + 1) / 2;
0526: h = (h + 1) / 2;
0527: }
0528: il.setWidth(w);
0529: il.setHeight(h);
0530:
0531: // Determine image opacity attributes.
0532: boolean hasAlpha = false;
0533: boolean isAlphaPremultiplied = false;
0534: cmd = new String[] { "OBJ=Colorspace," + resolution + ","
0535: + subImage };
0536: stream = postCommands(URLSpec, cmd);
0537: int colorSpaceIndex = 0;
0538: int numBands = 0;
0539: while ((label = getLabel(stream)) != null) {
0540: if (label.startsWith("colorspace")) {
0541: int[] ia = stringToIntArray(getDataAsString(stream,
0542: false));
0543: numBands = ia[3];
0544: switch (ia[2]) {
0545: case CS_MONOCHROME:
0546: colorSpaceIndex = ColorSpace.CS_GRAY;
0547: break;
0548: case CS_PHOTOYCC:
0549: colorSpaceIndex = ColorSpace.CS_PYCC;
0550: break;
0551: case CS_NIFRGB:
0552: colorSpaceIndex = ColorSpace.CS_sRGB;
0553: break;
0554: default:
0555: colorSpaceIndex = numBands < 3 ? ColorSpace.CS_GRAY
0556: : ColorSpace.CS_sRGB;
0557: }
0558: for (int j = 1; j <= numBands; j++) {
0559: if (ia[3 + j] == CS_PLANE_ALPHA) {
0560: hasAlpha = true;
0561: }
0562: }
0563: isAlphaPremultiplied = ia[1] == 1;
0564: } else {
0565: checkError(label, stream, true);
0566: }
0567: }
0568: closeStream(stream);
0569:
0570: // Set the ColorModel.
0571: ColorSpace cs = ColorSpace.getInstance(colorSpaceIndex);
0572: int dtSize = DataBuffer.getDataTypeSize(DataBuffer.TYPE_BYTE);
0573: int[] bits = new int[numBands];
0574: for (int i = 0; i < numBands; i++) {
0575: bits[i] = dtSize;
0576: }
0577: int transparency = hasAlpha ? Transparency.TRANSLUCENT
0578: : Transparency.OPAQUE;
0579: ColorModel cm = new ComponentColorModel(cs, bits, hasAlpha,
0580: isAlphaPremultiplied, transparency,
0581: DataBuffer.TYPE_BYTE);
0582: il.setColorModel(cm);
0583:
0584: // Set the SampleModel.
0585: int[] bandOffsets = new int[numBands];
0586: for (int i = 0; i < numBands; i++) {
0587: bandOffsets[i] = i;
0588: }
0589: il.setSampleModel(RasterFactory
0590: .createPixelInterleavedSampleModel(
0591: DataBuffer.TYPE_BYTE, TILE_SIZE, TILE_SIZE,
0592: numBands, numBands * TILE_SIZE, bandOffsets));
0593:
0594: return il;
0595: }
0596:
0597: /**
0598: * Construct an OpImage given a String representation of a URL,
0599: * a resolution, and a sub-image index.
0600: *
0601: * @param URLSpec The URL of the IIP image including the FIF cimmand
0602: * if needed and possibly an SDS command.
0603: * @param level The resolution level with 0 as the lowest resolution.
0604: * @param subImage The subimage number.
0605:
0606: * @param layout The layout hint; may be null.
0607: */
0608: public IIPResolutionOpImage(Map config, String URLSpec, int level,
0609: int subImage) {
0610: super ((Vector) null, // the image is sourceless
0611: layoutHelper(URLSpec, level, subImage), config, false);
0612:
0613: this .renderHints = (RenderingHints) config;
0614:
0615: // Cache the constructor parameters.
0616: URLString = URLSpec;
0617: this .subImage = subImage;
0618:
0619: // Retrieve required parameters from server.
0620: String[] cmd = new String[] { "OBJ=Resolution-number" };
0621: InputStream stream = postCommands(cmd);
0622: String label = null;
0623: while ((label = getLabel(stream)) != null) {
0624: if (label.equals("resolution-number")) {
0625: String data = getDataAsString(stream, false);
0626: int numRes = Integer.valueOf(data).intValue();
0627: if (level < 0) {
0628: resolution = 0;
0629: } else if (level >= numRes) {
0630: resolution = numRes - 1;
0631: } else {
0632: resolution = level;
0633: }
0634: } else {
0635: checkError(label, stream, true);
0636: }
0637: }
0638: endResponse(stream);
0639:
0640: // Cache some values which will be used repetitively.
0641: ColorSpace cs = colorModel.getColorSpace();
0642: if (cs.isCS_sRGB()) {
0643: colorSpaceType = CS_NIFRGB;
0644: } else if (cs
0645: .equals(ColorSpace.getInstance(ColorSpace.CS_GRAY))) {
0646: colorSpaceType = CS_MONOCHROME;
0647: } else {
0648: colorSpaceType = CS_PHOTOYCC;
0649: }
0650: hasAlpha = colorModel.hasAlpha();
0651: isAlphaPremultilpied = colorModel.isAlphaPremultiplied();
0652: minTileX = getMinTileX();
0653: minTileY = getMinTileY();
0654: numXTiles = getNumXTiles();
0655: }
0656:
0657: /*
0658: * Post the array of commands to the IIP server.
0659: */
0660: private InputStream postCommands(String[] commands) {
0661: return postCommands(URLString, commands);
0662: }
0663:
0664: /*
0665: * End reading the server response.
0666: */
0667: private void endResponse(InputStream stream) {
0668: // Add if-then block and handle socket connection.
0669: closeStream(stream);
0670: }
0671:
0672: /*
0673: * Compute the tile with the specified position in the tile grid.
0674: * Either a block of tiles or a single tile is retrieved depending
0675: * on where the requested tile false in the tile grid with respect
0676: * to the upper left tile in the image. All tiles except that returned
0677: * are stored in the TileCache.
0678: */
0679: public Raster computeTile(int tileX, int tileY) {
0680: Raster raster = null;
0681:
0682: // If the tile is the upper left tile of a block of tiles
0683: // where the blocks are counted from the upper left corner
0684: // of the image then retrieve a block of tiles.
0685: if ((tileX - minTileX) % tileBlockWidth == 0
0686: && (tileY - minTileY) % tileBlockHeight == 0) {
0687: int endTileX = tileX + tileBlockWidth - 1;
0688: if (endTileX > getMaxTileX()) {
0689: endTileX = getMaxTileX();
0690: }
0691: int endTileY = tileY + tileBlockHeight - 1;
0692: if (endTileY > getMaxTileY()) {
0693: endTileY = getMaxTileY();
0694: }
0695: raster = getTileBlock(tileX, tileY, endTileX, endTileY);
0696: } else if ((raster = getTileFromCache(tileX, tileY)) == null) {
0697: raster = getTileBlock(tileX, tileY, tileX, tileY);
0698: }
0699:
0700: return raster;
0701: }
0702:
0703: /*
0704: * Extract from the IIP response label the coordinates in the tile grid
0705: * of the tile returned by the IIP server.
0706: *
0707: * @param label The IIP response label.
0708: * @param xy The tile grid coordinates; may be null.
0709: */
0710: private Point getTileXY(String label, Point xy) {
0711: // Get the tile number from the IIP response label.
0712: int beginIndex = label.indexOf(",", label.indexOf(",") + 1) + 1;
0713: int endIndex = label.lastIndexOf(",");
0714: int tile = Integer.valueOf(
0715: label.substring(beginIndex, endIndex)).intValue();
0716:
0717: // Calculate the tile coordinates.
0718: int tileX = (tile + minTileX) % numXTiles;
0719: int tileY = (tile + minTileX - tileX) / numXTiles + minTileY;
0720:
0721: // Create or set the location.
0722: if (xy == null) {
0723: xy = new Point(tileX, tileY);
0724: } else {
0725: xy.setLocation(tileX, tileY);
0726: }
0727:
0728: return xy;
0729: }
0730:
0731: /*
0732: * Retrieve a block of tiles from the IIP server. All tiles except
0733: * the upper left tile are stored in the TileCache wheareas the upper
0734: * left tile is returned.
0735: */
0736: private Raster getTileBlock(int upperLeftTileX, int upperLeftTileY,
0737: int lowerRightTileX, int lowerRightTileY) {
0738: int startTile = (upperLeftTileY - minTileY) * numXTiles
0739: + upperLeftTileX - minTileX;
0740: int endTile = (lowerRightTileY - minTileY) * numXTiles
0741: + lowerRightTileX - minTileX;
0742:
0743: String cmd = null;
0744: if (startTile == endTile) { // single tile
0745: cmd = new String("til=" + resolution + "," + startTile
0746: + "," + subImage);
0747: } else { // range of tiles
0748: cmd = new String("til=" + resolution + "," + startTile
0749: + "-" + endTile + "," + subImage);
0750: }
0751: InputStream stream = postCommands(new String[] { cmd });
0752: int compressionType = -1;
0753: int compressionSubType = -1;
0754: byte[] data = null;
0755: String label = null;
0756: Raster upperLeftTile = null;
0757: Point tileXY = new Point();
0758: while ((label = getLabel(stream)) != null) {
0759: if (label.startsWith("tile")) {
0760: int length = getLength(stream);
0761:
0762: byte[] header = new byte[8];
0763: try {
0764: stream.read(header);
0765: } catch (Exception e) {
0766: throwIIPException(JaiI18N
0767: .getString("IIPResolutionOpImage1"));
0768: }
0769:
0770: length -= 8;
0771:
0772: compressionType = (int) ((header[3] << 24)
0773: | (header[2] << 16) | (header[1] << 8) | header[0]);
0774: compressionSubType = (int) ((header[7] << 24)
0775: | (header[6] << 16) | (header[5] << 8) | header[4]);
0776:
0777: if (length != 0) {
0778: data = new byte[length];
0779: try {
0780: int numBytesRead = 0;
0781: int offset = 0;
0782: do {
0783: numBytesRead = stream.read(data, offset,
0784: length - offset);
0785: offset += numBytesRead;
0786: } while (offset < length && numBytesRead != -1);
0787: if (numBytesRead != -1) {
0788: stream.read(); // CR
0789: stream.read(); // LF
0790: }
0791: } catch (Exception e) {
0792: throwIIPException(JaiI18N
0793: .getString("IIPResolutionOpImage2"));
0794: }
0795: }
0796:
0797: getTileXY(label, tileXY);
0798: int tileX = (int) tileXY.getX();
0799: int tileY = (int) tileXY.getY();
0800: int tx = tileXToX(tileX);
0801: int ty = tileYToY(tileY);
0802:
0803: Raster raster = null;
0804:
0805: switch (compressionType) {
0806: case TILE_UNCOMPRESSED:
0807: raster = getUncompressedTile(tx, ty, data);
0808: break;
0809: case TILE_SINGLE_COLOR:
0810: raster = getSingleColorTile(tx, ty,
0811: compressionSubType);
0812: break;
0813: case TILE_JPEG:
0814: raster = getJPEGTile(tx, ty, compressionSubType,
0815: data);
0816: break;
0817: case TILE_INVALID:
0818: default:
0819: raster = createWritableRaster(sampleModel,
0820: new Point(tx, ty));
0821: break;
0822: }
0823:
0824: if (tileX == upperLeftTileX && tileY == upperLeftTileY) {
0825: upperLeftTile = raster;
0826: } else {
0827: //System.out.println("Caching "+label);
0828: addTileToCache(tileX, tileY, raster);
0829: }
0830: } else {
0831: checkError(label, stream, true);
0832: }
0833: }
0834:
0835: endResponse(stream);
0836:
0837: return upperLeftTile;
0838: }
0839:
0840: /*
0841: * Create a Raster from the data of an uncompressed tile.
0842: */
0843: private Raster getUncompressedTile(int tx, int ty, byte[] data) {
0844: DataBuffer dataBuffer = new DataBufferByte(data, data.length);
0845:
0846: return Raster.createRaster(sampleModel, dataBuffer, new Point(
0847: tx, ty));
0848: }
0849:
0850: /*
0851: * Create a Raster of a single color.
0852: */
0853: private Raster getSingleColorTile(int tx, int ty, int color) {
0854: byte R = (byte) (color & 0x000000ff);
0855: byte G = (byte) ((color >> 8) & 0x000000ff);
0856: byte B = (byte) ((color >> 16) & 0x000000ff);
0857: byte A = (byte) ((color >> 24) & 0x000000ff);
0858:
0859: int numBands = sampleModel.getNumBands();
0860: int length = tileWidth * tileHeight * numBands;
0861: byte[] data = new byte[length];
0862: int i = 0;
0863: switch (numBands) {
0864: case 1:
0865: while (i < length) {
0866: data[i++] = R;
0867: }
0868: break;
0869: case 2:
0870: while (i < length) {
0871: data[i++] = R;
0872: data[i++] = A;
0873: }
0874: break;
0875: case 3:
0876: while (i < length) {
0877: data[i++] = R;
0878: data[i++] = G;
0879: data[i++] = B;
0880: }
0881: case 4:
0882: default:
0883: while (i < length) {
0884: data[i++] = R;
0885: data[i++] = G;
0886: data[i++] = B;
0887: data[i++] = A;
0888: }
0889: }
0890:
0891: DataBuffer dataBuffer = new DataBufferByte(data, data.length);
0892:
0893: return Raster.createRaster(sampleModel, dataBuffer, new Point(
0894: tx, ty));
0895: }
0896:
0897: /*
0898: * Create a Raster from a JPEG-compressed data stream.
0899: */
0900: private Raster getJPEGTile(int tx, int ty, int subType, byte[] data) {
0901: int tableIndex = (subType >> 24) & 0x000000ff;
0902: ;
0903: boolean colorConversion = (subType & 0x00ff0000) != 0;
0904: JPEGDecodeParam decodeParam = null;
0905: if (tableIndex != 0) {
0906: decodeParam = getJPEGDecodeParam(tableIndex);
0907: }
0908:
0909: ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
0910: JPEGImageDecoder decoder = decodeParam == null ? JPEGCodec
0911: .createJPEGDecoder(byteStream) : JPEGCodec
0912: .createJPEGDecoder(byteStream, decodeParam);
0913:
0914: Raster raster = null;
0915: try {
0916: raster = decoder.decodeAsRaster().createTranslatedChild(tx,
0917: ty);
0918: } catch (Exception e) {
0919:
0920: ImagingListener listener = ImageUtil
0921: .getImagingListener(renderHints);
0922: listener.errorOccurred(JaiI18N
0923: .getString("IIPResolutionOpImage3"),
0924: new ImagingException(e), this , false);
0925: /*
0926: String msg = JaiI18N.getString("IIPResolutionOpImage3")+" "+
0927: e.getMessage();
0928: throw new RuntimeException(msg);
0929: */
0930: }
0931: closeStream(byteStream);
0932:
0933: if (colorSpaceType == CS_NIFRGB && colorConversion) {
0934: YCbCrToNIFRGB(raster);
0935: }
0936:
0937: return raster;
0938: }
0939:
0940: /*
0941: * Retrieve the JPEGDecodeParam object for the indicated table. If the
0942: * object is available in the config, use it; otherwise retrieve it from
0943: * the server. An ArrayIndexOutOfBoundsException will be thrown if
0944: * the parameter is not in the range [1,256].
0945: */
0946: private synchronized JPEGDecodeParam getJPEGDecodeParam(
0947: int tableIndex) {
0948: JPEGDecodeParam decodeParam = decodeParamCache[tableIndex - 1];
0949:
0950: if (decodeParam == null) {
0951: String cmd = new String("OBJ=Comp-group," + TILE_JPEG + ","
0952: + tableIndex);
0953: InputStream stream = postCommands(new String[] { cmd });
0954: String label = null;
0955: while ((label = getLabel(stream)) != null) {
0956: if (label.startsWith("comp-group")) {
0957: byte[] table = getDataAsByteArray(stream);
0958: ByteArrayInputStream tableStream = new ByteArrayInputStream(
0959: table);
0960: JPEGImageDecoder decoder = JPEGCodec
0961: .createJPEGDecoder(tableStream);
0962: try {
0963: // This call is necessary.
0964: decoder.decodeAsRaster();
0965: } catch (Exception e) {
0966: // Ignore.
0967: }
0968: decodeParam = decoder.getJPEGDecodeParam();
0969: } else {
0970: checkError(label, stream, true);
0971: }
0972: }
0973:
0974: endResponse(stream);
0975:
0976: if (decodeParam != null) {
0977: decodeParamCache[tableIndex - 1] = decodeParam;
0978: }
0979: }
0980:
0981: return decodeParam;
0982: }
0983:
0984: /*
0985: * Obtain valid properties from IIP server and use
0986: * setProperty() to store the name/value pairs.
0987: */
0988: private synchronized void initializeIIPProperties() {
0989: if (!arePropertiesInitialized) {
0990: String[] cmd = new String[] { "OBJ=IIP", "OBJ=Basic-info",
0991: "OBJ=View-info", "OBJ=Summary-info",
0992: "OBJ=Copyright" };
0993: InputStream stream = postCommands(cmd);
0994: String label = null;
0995: while ((label = getLabel(stream)) != null) {
0996: String name = label;
0997: Object value = null;
0998: if (label.equals("error")) {
0999: flushData(stream, true);
1000: } else if (label.startsWith("colorspace")
1001: || label.equals("max-size")) {
1002: if (label.startsWith("colorspace")) {
1003: name = "colorspace";
1004: }
1005: value = stringToIntArray(getDataAsString(stream,
1006: false));
1007: } else if (label.equals("resolution-number")) {
1008: value = Integer.valueOf(getDataAsString(stream,
1009: false));
1010: } else if (label.equals("aspect-ratio")
1011: || label.equals("contrast-adjust")
1012: || label.equals("filtering-value")) {
1013: value = Float
1014: .valueOf(getDataAsString(stream, false));
1015: } else if (label.equals("affine-transform")) {
1016: float[] a = (float[]) stringToFloatArray(getDataAsString(
1017: stream, false));
1018: value = new AffineTransform(a[0], a[1], a[3], a[4],
1019: a[5], a[7]);
1020: } else if (label.equals("color-twist")) {
1021: value = stringToFloatArray(getDataAsString(stream,
1022: false));
1023: } else if (label.equals("roi")) {
1024: name = "roi-iip";
1025: float[] rect = stringToFloatArray(getDataAsString(
1026: stream, false));
1027: value = new Rectangle2D.Float(rect[0], rect[1],
1028: rect[2], rect[3]);
1029: } else if (label.equals("copyright")
1030: || label.equals("title")
1031: || label.equals("subject")
1032: || label.equals("author")
1033: || label.equals("keywords")
1034: || label.equals("comment")
1035: || label.equals("last-author")
1036: || label.equals("rev-number")
1037: || label.equals("app-name")) {
1038: value = getDataAsString(stream, true);
1039: } else if (label.equals("iip")
1040: || label.equals("iip-server")
1041: || label.equals("edit-time")
1042: || label.equals("last-printed")
1043: || label.equals("create-dtm")
1044: || label.equals("last-save-dtm")) {
1045: value = getDataAsString(stream, false);
1046: } else { // Ignore unknown objects
1047: flushData(stream, false);
1048: }
1049: if (name != null && value != null) {
1050: setProperty(name, value);
1051: }
1052: }
1053: endResponse(stream);
1054: arePropertiesInitialized = true;
1055: }
1056: }
1057:
1058: /*
1059: * Forward to superclass after lazy initialization of IIP properties.
1060: */
1061: public String[] getPropertyNames() {
1062: initializeIIPProperties();
1063: return super .getPropertyNames();
1064: }
1065:
1066: /*
1067: * Forward to superclass after lazy initialization of IIP properties.
1068: */
1069: public Object getProperty(String name) {
1070: initializeIIPProperties();
1071: return super .getProperty(name);
1072: }
1073:
1074: /**
1075: * Throws an IllegalArgumentException since the image has no image
1076: * sources.
1077: *
1078: * @param sourceRect ignored.
1079: * @param sourceIndex ignored.
1080: * @throws IllegalArgumentException since the image has no image sources.
1081: */
1082: public Rectangle mapSourceRect(Rectangle sourceRect, int sourceIndex) {
1083: throw new IllegalArgumentException(JaiI18N
1084: .getString("AreaOpImage0"));
1085: }
1086:
1087: /**
1088: * Throws an IllegalArgumentException since the image has no image
1089: * sources.
1090: *
1091: * @param destRect ignored.
1092: * @param sourceIndex ignored.
1093: * @throws IllegalArgumentException since the image has no image sources.
1094: */
1095: public Rectangle mapDestRect(Rectangle destRect, int sourceIndex) {
1096: throw new IllegalArgumentException(JaiI18N
1097: .getString("AreaOpImage0"));
1098: }
1099:
1100: /**
1101: * Dispose of any allocated resources.
1102: */
1103: protected void finalize() throws Throwable {
1104: super.finalize();
1105: }
1106: }
|