0001: /*
0002: * $RCSfile: IIPCRIF.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.Image;
0015: import java.awt.Rectangle;
0016: import java.awt.RenderingHints;
0017: import java.awt.Shape;
0018: import java.awt.color.ColorSpace;
0019: import java.awt.geom.AffineTransform;
0020: import java.awt.geom.Point2D;
0021: import java.awt.geom.Rectangle2D;
0022: import java.awt.image.ColorModel;
0023: import java.awt.image.ComponentColorModel;
0024: import java.awt.image.RenderedImage;
0025: import java.awt.image.renderable.ParameterBlock;
0026: import java.awt.image.renderable.RenderableImage;
0027: import java.awt.image.renderable.RenderableImageOp;
0028: import java.awt.image.renderable.RenderContext;
0029: import java.io.InputStream;
0030: import java.net.URL;
0031: import java.util.Vector;
0032: import javax.media.jai.CRIFImpl;
0033: import javax.media.jai.EnumeratedParameter;
0034: import javax.media.jai.ImageLayout;
0035: import javax.media.jai.Interpolation;
0036: import javax.media.jai.JAI;
0037: import javax.media.jai.LookupTableJAI;
0038: import javax.media.jai.MultiResolutionRenderableImage;
0039: import javax.media.jai.PlanarImage;
0040: import javax.media.jai.ROI;
0041: import javax.media.jai.ROIShape;
0042: import javax.media.jai.TiledImage;
0043: import javax.media.jai.operator.TransposeDescriptor;
0044: import javax.media.jai.util.ImagingException;
0045: import javax.media.jai.util.ImagingListener;
0046: import com.sun.media.jai.codec.ImageCodec;
0047: import com.sun.media.jai.codec.ImageDecoder;
0048: import com.sun.media.jai.codec.MemoryCacheSeekableStream;
0049: import com.sun.media.jai.util.ImageUtil;
0050:
0051: /**
0052: * This CRIF implements the "iip" operation in the rendered and renderable
0053: * image layers.
0054: *
0055: * <p> In renderable mode this operation is designed to execute on the
0056: * server as many composed operations (those specified via parameters) as
0057: * the server's capability permits. In the 1.0 implementation all operations
0058: * are actually carried out on the client: server-side processing will be
0059: * added in a subsequent release.
0060: *
0061: * <p> Rendered operation returns the default rendering of renderable mode
0062: * operation.
0063: *
0064: * <p> The actual set of composed operations is described in section 2.2.1.1
0065: * "Composed Image Commands" ot the "Internet Imaging Protocol Specification"
0066: * version 1.0.5. The sequence in which these commands are to be applied is
0067: * also described in this section.
0068: *
0069: * <p> More detailed information actually required to understand and implement
0070: * the composed operations is available in the "FlashPix Format Specification"
0071: * version 1.0.1. Of particular interest are Section 2 "Image Data
0072: * Representation", Section 5.3.3 "Relationship of NIF RGB to sRGB",
0073: * Section 5.4 "Relating PhotoYCC to NIG RGB", Section 7.2 "Viewing Transform
0074: * Parameters", and Section 7.3 "Sequence of Viewing Parameter
0075: * Transformations". Also of note are Tables 3.6 and 3.7.
0076: *
0077: * @since 1.0
0078: *
0079: * @see IIPDescriptor
0080: * @see IIPResolutionRIF
0081: * @see IIPResolutionOpImage
0082: * @see <a href="http://www.digitalimaging.org">Digital Imaging Group</a>
0083: *
0084: */
0085: public class IIPCRIF extends CRIFImpl {
0086: // Bitmask constants indicating supplied parameters.
0087: private static final int MASK_FILTER = 0x1;
0088: private static final int MASK_COLOR_TWIST = 0x2;
0089: private static final int MASK_CONTRAST = 0x4;
0090: private static final int MASK_ROI_SOURCE = 0x8;
0091: private static final int MASK_TRANSFORM = 0x10;
0092: private static final int MASK_ASPECT_RATIO = 0x20;
0093: private static final int MASK_ROI_DESTINATION = 0x40;
0094: private static final int MASK_ROTATION = 0x80;
0095: private static final int MASK_MIRROR_AXIS = 0x100;
0096: private static final int MASK_ICC_PROFILE = 0x200;
0097: private static final int MASK_JPEG_QUALITY = 0x400;
0098: private static final int MASK_JPEG_TABLE = 0x800;
0099:
0100: // Constants indicating server vendors.
0101: private static final int VENDOR_HP = 0;
0102: private static final int VENDOR_LIVE_PICTURE = 1;
0103: private static final int VENDOR_KODAK = 2;
0104: private static final int VENDOR_UNREGISTERED = 255;
0105: private static final int VENDOR_EXPERIMENTAL = 999;
0106:
0107: // Bitmask constants indicating server capabilities
0108: private static final int SERVER_CVT_JPEG = 0x1;
0109: private static final int SERVER_CVT_FPX = 0x2;
0110: private static final int SERVER_CVT_MJPEG = 0x4;
0111: private static final int SERVER_CVT_MFPX = 0x8;
0112: private static final int SERVER_CVT_M2JPEG = 0x10;
0113: private static final int SERVER_CVT_M2FPX = 0x20;
0114: private static final int SERVER_CVT_JTL = 0x40;
0115:
0116: // Special bitmask combinations
0117: private static final int SERVER_JPEG_PARTIAL = SERVER_CVT_JPEG
0118: | SERVER_CVT_MJPEG;
0119: private static final int SERVER_JPEG_FULL = SERVER_JPEG_PARTIAL
0120: | SERVER_CVT_M2JPEG;
0121: private static final int SERVER_FPX_PARTIAL = SERVER_CVT_FPX
0122: | SERVER_CVT_MFPX;
0123: private static final int SERVER_FPX_FULL = SERVER_FPX_PARTIAL
0124: | SERVER_CVT_M2FPX;
0125:
0126: // --- RGB[A] <-> PhotoYCC[A] metric conversion matrices ---
0127: // As stated in the FlashPix specification, these matrices are
0128: // sufficient for tone and color correction but should not be
0129: // used for actual color space conversion calculations.
0130:
0131: // PhotoYCCA -> RGBA metric color conversion matrix
0132: private static final double[][] YCCA_TO_RGBA = new double[][] {
0133: { 1.358400, 0.000000, 1.821500, 0.000000 },
0134: { 1.358400, -0.430300, -0.927100, 0.000000 },
0135: { 1.358400, 2.217900, 0.000000, 0.000000 },
0136: { 0.000000, 0.000000, 0.000000, 1.000000 } };
0137:
0138: // PhotoYCCA -> RGBA metric color conversion constant
0139: private static final double[][] YCCA_TO_RGBA_CONST = new double[][] {
0140: { -249.55 }, { 194.14 }, { -345.99 }, { 0.0 } };
0141:
0142: // RGBA -> PhotoYCCA metric color conversion matrix
0143: private static final double[][] RGBA_TO_YCCA = new double[][] {
0144: { 0.220018, 0.432276, 0.083867, 0.000000 },
0145: { -0.134755, -0.264756, 0.399511, 0.000000 },
0146: { 0.384918, -0.322373, -0.062544, 0.000000 },
0147: { 0.000000, 0.000000, 0.000000, 1.000000 } };
0148:
0149: // RGBA -> PhotoYCCA metric color conversion constant
0150: private static final double[][] RGBA_TO_YCCA_CONST = new double[][] {
0151: { 0.0005726 }, { 155.9984 }, { 137.0022 }, { 0.0 } };
0152:
0153: // PhotoYCC -> RGB metric color conversion matrix
0154: private static final double[][] YCC_TO_RGB = new double[][] {
0155: { 1.358400, 0.000000, 1.821500 },
0156: { 1.358400, -0.430300, -0.927100 },
0157: { 1.358400, 2.217900, 0.000000 } };
0158:
0159: // PhotoYCC -> RGB metric color conversion constant
0160: private static final double[][] YCC_TO_RGB_CONST = new double[][] {
0161: { -249.55 }, { 194.14 }, { -345.99 } };
0162:
0163: // RGB -> PhotoYCC metric color conversion matrix
0164: private static final double[][] RGB_TO_YCC = new double[][] {
0165: { 0.220018, 0.432276, 0.083867 },
0166: { -0.134755, -0.264756, 0.399511 },
0167: { 0.384918, -0.322373, -0.062544 } };
0168:
0169: // RGB -> PhotoYCC metric color conversion constant
0170: private static final double[][] RGB_TO_YCC_CONST = new double[][] {
0171: { 0.0005726 }, { 155.9984 }, { 137.0022 } };
0172:
0173: /**
0174: * Returns the operation mask based on the supplied parameters.
0175: */
0176: private static final int getOperationMask(ParameterBlock pb) {
0177: int opMask = 0;
0178:
0179: // Initialize the operation mask according to which
0180: // parameters are actually supplied.
0181: if (pb.getFloatParameter(2) != 0.0F) {
0182: opMask |= MASK_FILTER;
0183: }
0184: if (pb.getObjectParameter(3) != null) {
0185: opMask |= MASK_COLOR_TWIST;
0186: }
0187: if (Math.abs(pb.getFloatParameter(4) - 1.0F) > 0.01F) {
0188: opMask |= MASK_CONTRAST;
0189: }
0190: if (pb.getObjectParameter(5) != null) {
0191: opMask |= MASK_ROI_SOURCE;
0192: }
0193: AffineTransform tf = (AffineTransform) pb.getObjectParameter(6);
0194: if (!tf.isIdentity()) {
0195: opMask |= MASK_TRANSFORM;
0196: }
0197: if (pb.getObjectParameter(7) != null) {
0198: opMask |= MASK_ASPECT_RATIO;
0199: }
0200: if (pb.getObjectParameter(8) != null) {
0201: opMask |= MASK_ROI_DESTINATION;
0202: }
0203: if (pb.getIntParameter(9) != 0) {
0204: opMask |= MASK_ROTATION;
0205: }
0206: if (pb.getObjectParameter(10) != null) {
0207: opMask |= MASK_MIRROR_AXIS;
0208: }
0209: if (pb.getObjectParameter(11) != null) {
0210: opMask |= MASK_ICC_PROFILE;
0211: }
0212: if (pb.getObjectParameter(12) != null) {
0213: opMask |= MASK_JPEG_QUALITY;
0214: }
0215: if (pb.getObjectParameter(13) != null) {
0216: opMask |= MASK_JPEG_TABLE;
0217: }
0218:
0219: return opMask;
0220: }
0221:
0222: /**
0223: * Returns the server capability mask.
0224: */
0225: private static final int getServerCapabilityMask(String URLSpec,
0226: RenderedImage lowRes) {
0227: int vendorID = 255; // Unregistered vendor.
0228: int serverMask = 0;
0229:
0230: // Get the server bitmask from the properties of the thumbnail image.
0231: if (lowRes.getProperty("iip-server") != null
0232: && lowRes.getProperty("iip-server") != Image.UndefinedProperty) {
0233: String serverString = (String) lowRes
0234: .getProperty("iip-server");
0235: int dot = serverString.indexOf(".");
0236: vendorID = Integer.valueOf(serverString.substring(0, dot))
0237: .intValue();
0238: serverMask = Integer.valueOf(
0239: serverString.substring(dot + 1)).intValue();
0240: }
0241:
0242: // If the vendor is not one the three that defined the IIP
0243: // specification then assume that the response to the OBJ=IIP-server
0244: // command is inaccurate. This may not be true in general but it
0245: // is true of the only other IIP server tested with this code.
0246: if (serverMask != 127 && vendorID != VENDOR_HP
0247: && vendorID != VENDOR_LIVE_PICTURE
0248: && vendorID != VENDOR_KODAK) {
0249: int[] maxSize = (int[]) lowRes.getProperty("max-size");
0250: String rgn = "&RGN=0.0,0.0," + (64.0F / maxSize[0]) + ","
0251: + (64.0F / maxSize[1]);
0252:
0253: // Actually test these capabilities
0254: if (canDecode(URLSpec, "&CNT=0.9&WID=64&CVT=JPEG", "JPEG")) {
0255: // CVT-JPEG && CVT-MJPEG && CVT-M2JPEG
0256: serverMask = SERVER_JPEG_FULL;
0257: } else if (canDecode(URLSpec, "&CNT=0.9&WID=64&CVT=FPX",
0258: "FPX")) {
0259: // CVT-FPX && CVT-MFPX && CVT-M2FPX
0260: serverMask = SERVER_FPX_FULL;
0261: } else if (canDecode(URLSpec, rgn + "&CVT=JPEG", "JPEG")) {
0262: // CVT-JPEG && CVT-MJPEG
0263: serverMask = SERVER_JPEG_PARTIAL;
0264: } else if (canDecode(URLSpec, rgn + "&CVT=FPX", "FPX")) {
0265: // CVT-FPX && CVT-MFPX
0266: serverMask = SERVER_FPX_PARTIAL;
0267: }
0268: }
0269:
0270: return serverMask;
0271: }
0272:
0273: /**
0274: * Test whether an image can be decoded from an IIP CVT URL.
0275: *
0276: * @param base The base IIP URL including the image specification.
0277: * @param suffix The IIP URL suffix including the CVT string.
0278: * @param fmt The desired format: "JPEG" or "FPX".
0279: * @return Whether the returned stream can be dedoced successfully.
0280: */
0281: private static boolean canDecode(String base, String suffix,
0282: String fmt) {
0283: StringBuffer buf = new StringBuffer(base);
0284:
0285: URL url = null;
0286: InputStream stream = null;
0287: RenderedImage rendering = null;
0288:
0289: boolean itWorks = false;
0290:
0291: try {
0292: buf.append(suffix);
0293: url = new URL(buf.toString());
0294: stream = url.openStream();
0295: ImageDecoder decoder = ImageCodec.createImageDecoder(fmt,
0296: stream, null);
0297: rendering = decoder.decodeAsRenderedImage();
0298: itWorks = true;
0299: } catch (Exception e) {
0300: itWorks = false; // redundant
0301: }
0302:
0303: return itWorks;
0304: }
0305:
0306: /**
0307: * Multiply two matrix parameters and return the result. The number of
0308: * columns of the first parameter must equal the number of rows of
0309: * the second parameter. The result will have the same number of rows
0310: * as the first parameter and the same number of columns as the
0311: * second parameter.
0312: */
0313: private static final double[][] matrixMultiply(double[][] A,
0314: double[][] B) {
0315: if (A[0].length != B.length) {
0316: throw new RuntimeException(JaiI18N.getString("IIPCRIF0"));
0317: }
0318:
0319: int nRows = A.length;
0320: int nCols = B[0].length;
0321: double[][] C = new double[nRows][nCols];
0322:
0323: int nSum = A[0].length;
0324: for (int r = 0; r < nRows; r++) {
0325: for (int c = 0; c < nCols; c++) {
0326: C[r][c] = 0.0;
0327: for (int k = 0; k < nSum; k++) {
0328: C[r][c] += A[r][k] * B[k][c];
0329: }
0330: }
0331: }
0332:
0333: return C;
0334: }
0335:
0336: /**
0337: * Compose a matrix A and a vector b into an array suitable for the
0338: * "Bandcombine" operation. The number of rows in the matrix must
0339: * equal the number of elements in the vector.
0340: */
0341: private static final double[][] composeMatrices(double[][] A,
0342: double[][] b) {
0343: int nRows = A.length;
0344: if (nRows != b.length) {
0345: throw new RuntimeException(JaiI18N.getString("IIPCRIF1"));
0346: } else if (b[0].length != 1) {
0347: throw new RuntimeException(JaiI18N.getString("IIPCRIF2"));
0348: }
0349: int nCols = A[0].length;
0350:
0351: double[][] bcMatrix = new double[nRows][nCols + 1];
0352:
0353: for (int r = 0; r < nRows; r++) {
0354: for (int c = 0; c < nCols; c++) {
0355: bcMatrix[r][c] = A[r][c];
0356: }
0357: bcMatrix[r][nCols] = b[r][0];
0358: }
0359:
0360: return bcMatrix;
0361: }
0362:
0363: /**
0364: * Generate a matrix which can perform the composite mapping from the
0365: * original color space to normalized Photo YCC, apply the color-twist
0366: * transformation, and return normalized Photo YCC to the original
0367: * color space including casting down opacity and chroma channels where
0368: * appropriate.
0369: */
0370: private static final double[][] getColorTwistMatrix(
0371: ColorModel colorModel, ParameterBlock pb) {
0372: // Convert color-twist matrix to 2D form.
0373: float[] ctwParam = (float[]) pb.getObjectParameter(3);
0374: double[][] ctw = new double[4][4];
0375: int k = 0;
0376: for (int r = 0; r < 4; r++) {
0377: for (int c = 0; c < 4; c++) {
0378: ctw[r][c] = ctwParam[k++];
0379: }
0380: }
0381:
0382: // Calculate composed metric color conversion/color-twist matrix H
0383: // and constant d.
0384: double[][] H = null;
0385: double[][] d = null;
0386: int csType = colorModel.getColorSpace().getType();
0387: if (csType == ColorSpace.TYPE_GRAY
0388: || csType == ColorSpace.TYPE_RGB) {
0389: // Calculate RGBA->YCCA->CTW->RGBA composed matix.
0390: H = matrixMultiply(matrixMultiply(YCCA_TO_RGBA, ctw),
0391: RGBA_TO_YCCA);
0392: d = YCCA_TO_RGBA_CONST;
0393: } else { // PYCC
0394: H = ctw;
0395: d = new double[][] { { 0.0 }, { 0.0 }, { 0.0 }, { 0.0 } };
0396: }
0397:
0398: // Calculate matrix A and vector b to cast data upwards to 4 bands.
0399: double[][] A = null;
0400: double[][] b = null;
0401: if (csType == ColorSpace.TYPE_GRAY) {
0402: if (colorModel.hasAlpha()) {
0403: A = new double[][] { { 1.0, 0.0 }, { 1.0, 0.0 },
0404: { 1.0, 0.0 }, { 0.0, 1.0 } };
0405: b = new double[][] { { 0.0 }, { 0.0 }, { 0.0 }, { 0.0 } };
0406: } else {
0407: A = new double[][] { { 1.0 }, { 1.0 }, { 1.0 }, { 0.0 } };
0408: b = new double[][] { { 0.0 }, { 0.0 }, { 0.0 },
0409: { 255.0 } };
0410: }
0411: } else if (!colorModel.hasAlpha()) { // RGB or YCC (no alpha)
0412: A = new double[][] { { 1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 },
0413: { 0.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0 } };
0414: b = new double[][] { { 0.0 }, { 0.0 }, { 0.0 }, { 255.0 } };
0415: } else { // RGBA or YCCA
0416: A = new double[][] { { 1.0, 0.0, 0.0, 0.0 },
0417: { 0.0, 1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0, 0.0 },
0418: { 0.0, 0.0, 0.0, 1.0 } };
0419: b = new double[][] { { 0.0 }, { 0.0 }, { 0.0 }, { 0.0 } };
0420: }
0421:
0422: // Determine whether chroma or opacity may be deleted.
0423: boolean truncateChroma = false;
0424: if (csType == ColorSpace.TYPE_GRAY && ctwParam[4] == 0.0F
0425: && ctwParam[7] == 0.0F && ctwParam[8] == 0.0F
0426: && ctwParam[11] == 0.0F) {
0427: truncateChroma = true;
0428: }
0429: boolean truncateAlpha = false;
0430: if (!colorModel.hasAlpha() && ctwParam[15] == 1.0F) {
0431: truncateAlpha = true;
0432: }
0433:
0434: // Calculate matrix T to truncate data down to alpha-less or
0435: // chroma-less data as appropriate.
0436: double[][] T = null;
0437: if (truncateAlpha && truncateChroma) {
0438: T = new double[][] { { 1.0, 0.0, 0.0, 0.0 } };
0439: } else if (truncateChroma) {
0440: T = new double[][] { { 1.0, 0.0, 0.0, 0.0 },
0441: { 0.0, 0.0, 0.0, 1.0 } };
0442: } else if (truncateAlpha) {
0443: T = new double[][] { { 1.0, 0.0, 0.0, 0.0 },
0444: { 0.0, 1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0, 0.0 } };
0445: } else { // Retain all bands
0446: T = new double[][] { { 1.0, 0.0, 0.0, 0.0 },
0447: { 0.0, 1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0, 0.0 },
0448: { 0.0, 0.0, 0.0, 1.0 } };
0449: }
0450:
0451: // Combine the matrices and vectors to get the overall transform.
0452: double[][] TH = matrixMultiply(T, H);
0453: double[][] THA = matrixMultiply(TH, A);
0454: double[][] THb = matrixMultiply(TH, b);
0455: double[][] THd = matrixMultiply(TH, d);
0456: double[][] Td = matrixMultiply(T, d);
0457:
0458: for (int r = 0; r < THb.length; r++) {
0459: for (int c = 0; c < THb[r].length; c++) {
0460: THb[r][c] += Td[r][c] - THd[r][c];
0461: }
0462: }
0463:
0464: // Compose the results into a form appropriate for "BandCombine".
0465: return composeMatrices(THA, THb);
0466: }
0467:
0468: /**
0469: * Creates a lookup table for the contrast operation. This is to be
0470: * applied to grayscale or RGB data possibly with an alpha channel.
0471: */
0472: private static final LookupTableJAI createContrastLUT(float K,
0473: int numBands) {
0474: byte[] contrastTable = new byte[256];
0475:
0476: double p = 0.43F;
0477:
0478: // Generate the LUT to be applied to the color band(s).
0479: for (int i = 0; i < 256; i++) {
0480: float j = (float) (i - 127.5F) / 255.0F;
0481: float f = 0.0F;
0482: if (j < 0.0F) {
0483: f = (float) (-p * Math.pow(-j / p, K));
0484: } else if (j > 0.0F) {
0485: f = (float) (p * Math.pow(j / p, K));
0486: }
0487: int val = (int) (f * 255.0F + 127.5F);
0488: if (val < 0) {
0489: contrastTable[i] = 0;
0490: } else if (val > 255) {
0491: contrastTable[i] = (byte) 255;
0492: } else {
0493: contrastTable[i] = (byte) (val & 0x000000ff);
0494: }
0495: }
0496:
0497: // Allocate LUT memory.
0498: byte[][] data = new byte[numBands][];
0499:
0500: // Set all LUT color bands to the same previously calculated table.
0501: // If alpha is present, set the LUT for it to a ramp.
0502: if (numBands % 2 == 1) { // no alpha channel present
0503: for (int i = 0; i < numBands; i++) {
0504: data[i] = contrastTable;
0505: }
0506: } else { // alpha channel present
0507: for (int i = 0; i < numBands - 1; i++) {
0508: data[i] = contrastTable;
0509: }
0510: data[numBands - 1] = new byte[256];
0511: byte[] b = data[numBands - 1];
0512: for (int i = 0; i < 256; i++) {
0513: b[i] = (byte) i;
0514: }
0515: }
0516:
0517: return new LookupTableJAI(data);
0518: }
0519:
0520: /** Constructor. */
0521: public IIPCRIF() {
0522: super ("IIP");
0523: }
0524:
0525: /**
0526: * Performs all operations on the server.
0527: */
0528: private RenderedImage serverProc(int serverMask,
0529: RenderContext renderContext, ParameterBlock paramBlock,
0530: int opMask, RenderedImage lowRes) {
0531: // Ensure that one of the four expected combinations obtains.
0532: if ((serverMask & SERVER_JPEG_FULL) != SERVER_JPEG_FULL
0533: && (serverMask & SERVER_FPX_FULL) != SERVER_FPX_FULL
0534: && (serverMask & SERVER_JPEG_PARTIAL) != SERVER_JPEG_PARTIAL
0535: && (serverMask & SERVER_FPX_PARTIAL) != SERVER_FPX_PARTIAL) {
0536: return null;
0537: }
0538:
0539: ImagingListener listener = ImageUtil
0540: .getImagingListener(renderContext);
0541:
0542: // Set JPEG and full server flags.
0543: boolean isJPEG = false;
0544: boolean isFull = false;
0545: if ((serverMask & SERVER_JPEG_FULL) == SERVER_JPEG_FULL) {
0546: isJPEG = isFull = true;
0547: } else if ((serverMask & SERVER_FPX_FULL) == SERVER_FPX_FULL) {
0548: isJPEG = false;
0549: isFull = true;
0550: } else if ((serverMask & SERVER_JPEG_PARTIAL) == SERVER_JPEG_PARTIAL) {
0551: isJPEG = true;
0552: isFull = false;
0553: }
0554:
0555: // Create a StringBuffer for the composed image command URL.
0556: StringBuffer buf = new StringBuffer((String) paramBlock
0557: .getObjectParameter(0));
0558: //TODO: subImages (how?)
0559:
0560: // Filtering.
0561: if ((opMask & MASK_FILTER) != 0) {
0562: buf.append("&FTR=" + paramBlock.getFloatParameter(2));
0563: }
0564:
0565: // Color-twist.
0566: if ((opMask & MASK_COLOR_TWIST) != 0) {
0567: buf.append("&CTW=");
0568: float[] ctw = (float[]) paramBlock.getObjectParameter(3);
0569: for (int i = 0; i < ctw.length; i++) {
0570: buf.append(ctw[i]);
0571: if (i != ctw.length - 1) {
0572: buf.append(",");
0573: }
0574: }
0575: }
0576:
0577: // Contrast.
0578: if ((opMask & MASK_CONTRAST) != 0) {
0579: buf.append("&CNT=" + paramBlock.getFloatParameter(4));
0580: }
0581:
0582: // Source rectangle of interest.
0583: if ((opMask & MASK_ROI_SOURCE) != 0) {
0584: Rectangle2D roi = (Rectangle2D) paramBlock
0585: .getObjectParameter(5);
0586: buf.append("&ROI=" + roi.getX() + "," + roi.getY() + ","
0587: + roi.getWidth() + "," + roi.getHeight());
0588: }
0589:
0590: // If full support for the CVT command is available, decompose the
0591: // AffineTransform specifying the transformation from renderable to
0592: // rendered coordinates into a translation, a pure scale, and the
0593: // residual transformation. The residual transformation may then be
0594: // concatenated with the server-side affine transform (after
0595: // inversion), the pure scale may be effected by specifying the WID
0596: // and HEI composed image command modifiers, and the translation as
0597: // a subsequent operation. If the WID and HEI modifiers are not
0598: // available, i.e., the server support is partial, this becomes
0599: // more problematic. Fortunately no such servers are known to exist.
0600:
0601: // Initialize the post-processing transform to the identity.
0602: AffineTransform postTransform = new AffineTransform();
0603:
0604: // Retrieve (a clone of) the renderable-to-rendered mapping.
0605: AffineTransform at = (AffineTransform) renderContext
0606: .getTransform().clone();
0607:
0608: // If the translation is non-zero set the post-transform.
0609: if (at.getTranslateX() != 0.0 || at.getTranslateY() != 0.0) {
0610: postTransform.setToTranslation(at.getTranslateX(), at
0611: .getTranslateY());
0612: double[] m = new double[6];
0613: at.getMatrix(m);
0614: at.setTransform(m[0], m[1], m[2], m[3], 0.0, 0.0);
0615: }
0616:
0617: // Determine the renderable destination region of interest.
0618: Rectangle2D rgn = null;
0619: if ((opMask & MASK_ROI_DESTINATION) != 0) {
0620: rgn = (Rectangle2D) paramBlock.getObjectParameter(8);
0621: } else {
0622: float aspectRatio = 1.0F;
0623: if ((opMask & MASK_ASPECT_RATIO) != 0) {
0624: aspectRatio = paramBlock.getFloatParameter(7);
0625: } else {
0626: aspectRatio = ((Float) (lowRes
0627: .getProperty("aspect-ratio"))).floatValue();
0628: }
0629: rgn = new Rectangle2D.Float(0.0F, 0.0F, aspectRatio, 1.0F);
0630: }
0631:
0632: // Apply the renderable-to-rendered mapping to the renderable
0633: // destination region of interest.
0634: Rectangle dstROI = at.createTransformedShape(rgn).getBounds();
0635:
0636: // Calculate the pure scale portion of the
0637: // renderable-to-rendered mapping.
0638: AffineTransform scale = AffineTransform.getScaleInstance(dstROI
0639: .getWidth()
0640: / rgn.getWidth(), dstROI.getHeight() / rgn.getHeight());
0641:
0642: // Determine the residual mapping.
0643: try {
0644: at.preConcatenate(scale.createInverse());
0645: } catch (Exception e) {
0646: String message = JaiI18N.getString("IIPCRIF6");
0647: listener.errorOccurred(message, new ImagingException(
0648: message, e), this , false);
0649: // throw new RuntimeException(JaiI18N.getString("IIPCRIF6"));
0650: }
0651:
0652: // Compose the inverse residual mapping with the renderable
0653: // transform.
0654: AffineTransform afn = (AffineTransform) paramBlock
0655: .getObjectParameter(6);
0656: try {
0657: afn.preConcatenate(at.createInverse());
0658: } catch (Exception e) {
0659: String message = JaiI18N.getString("IIPCRIF6");
0660: listener.errorOccurred(message, new ImagingException(
0661: message, e), this , false);
0662: // throw new RuntimeException(JaiI18N.getString("IIPCRIF6"));
0663: }
0664:
0665: if (isFull) {
0666: // Append the WID and HEI composed image command modifiers using
0667: // the dimensions of the rendered destination region of interest.
0668: buf
0669: .append("&WID=" + dstROI.width + "&HEI="
0670: + dstROI.height);
0671: /* XXX Begin suppressed section.
0672: } else if((opMask & MASK_TRANSFORM) != 0) {
0673: Point2D[] dstPts =
0674: new Point2D[] {new Point2D.Double(rgn.getMinX(),
0675: rgn.getMinY()),
0676: new Point2D.Double(rgn.getMaxX(),
0677: rgn.getMinY()),
0678: new Point2D.Double(rgn.getMinX(),
0679: rgn.getMaxY())};
0680: Point2D[] srcPts = new Point2D[3];
0681: afn.transform(dstPts, 0, srcPts, 0, 3);
0682:
0683: double LLeft = srcPts[0].distance(srcPts[2]);
0684: double LTop = srcPts[0].distance(srcPts[1]);
0685:
0686: int[] maxSize = (int[])lowRes.getProperty("max-size");
0687:
0688: double H = maxSize[1]*LLeft;
0689: double W = maxSize[1]*LTop;
0690:
0691: double m = Math.max(H, W*(double)maxSize[1]/(double)maxSize[0]);
0692:
0693: int Hp = (int)(m + 0.5);
0694: int Wp = (int)(m*(double)maxSize[0]/(double)maxSize[1] + 0.5);
0695: System.out.println("Estimated dimensions = "+Wp+" x "+Hp);
0696:
0697: AffineTransform scl =
0698: AffineTransform.getScaleInstance(dstROI.getWidth()/Wp,
0699: dstROI.getHeight()/Hp);
0700: System.out.println("scl = "+scl);
0701: afn.preConcatenate(scl);
0702: End suppressed section. XXX */
0703: }
0704:
0705: // Append the affine tranform composed image command.
0706: double[] matrix = new double[6];
0707: afn.getMatrix(matrix);
0708: buf.append("&AFN=" + matrix[0] + "," + matrix[2] + ",0,"
0709: + matrix[4] + "," + matrix[1] + "," + matrix[3] + ",0,"
0710: + matrix[5] + ",0,0,1,0,0,0,0,1");
0711:
0712: // Destination aspect ratio.
0713: if ((opMask & MASK_ASPECT_RATIO) != 0) {
0714: buf.append("&RAR=" + paramBlock.getFloatParameter(7));
0715: }
0716:
0717: // Destination rectangle of interest.
0718: if ((opMask & MASK_ROI_DESTINATION) != 0) {
0719: Rectangle2D dstRGN = (Rectangle2D) paramBlock
0720: .getObjectParameter(8);
0721: buf.append("&RGN=" + dstRGN.getX() + "," + dstRGN.getY()
0722: + "," + dstRGN.getWidth() + ","
0723: + dstRGN.getHeight());
0724: }
0725:
0726: // Rotation and mirroring.
0727: if (isFull) {
0728: if ((opMask & MASK_ROTATION) != 0
0729: || (opMask & MASK_MIRROR_AXIS) != 0) {
0730: buf.append("&RFM=" + paramBlock.getIntParameter(9));
0731: if ((opMask & MASK_MIRROR_AXIS) != 0) {
0732: String axis = (String) paramBlock
0733: .getObjectParameter(10);
0734: if (axis.equalsIgnoreCase("x")) {
0735: buf.append(",0");
0736: } else {
0737: buf.append(",90");
0738: }
0739: }
0740: }
0741: }
0742:
0743: // ICC profile.
0744: if ((opMask & MASK_ICC_PROFILE) != 0) {
0745: // According to the IIP specification this is not supported
0746: // over HTTP connections and that is all that is available from
0747: // the vendors right now, i.e., no socket connections are
0748: // available (includes LivePicture and TrueSpectra).
0749: }
0750:
0751: // JPEG quality and compression group index.
0752: if (isJPEG) {
0753: if ((opMask & MASK_JPEG_QUALITY) != 0) {
0754: buf.append("&QLT=" + paramBlock.getIntParameter(12));
0755: }
0756:
0757: if ((opMask & MASK_JPEG_TABLE) != 0) {
0758: buf.append("&CIN=" + paramBlock.getIntParameter(13));
0759: }
0760: }
0761:
0762: // Set the format string.
0763: String format = isJPEG ? "JPEG" : "FPX";
0764:
0765: // Append the CVT command.
0766: buf.append("&CVT=" + format);
0767:
0768: // Create a URL with the CVT string, open a stream from it, and
0769: // decode the image using the appropriate decoder.
0770: InputStream stream = null;
0771: RenderedImage rendering = null;
0772: try {
0773: URL url = new URL(buf.toString());
0774: stream = url.openStream();
0775: MemoryCacheSeekableStream sStream = new MemoryCacheSeekableStream(
0776: stream);
0777: rendering = JAI.create(format, sStream);
0778: } catch (Exception e) {
0779: String message = JaiI18N.getString("IIPCRIF7") + " "
0780: + buf.toString();
0781: listener.errorOccurred(message, new ImagingException(
0782: message, e), this , false);
0783: // throw new RuntimeException(e.getClass()+" "+e.getMessage());
0784: }
0785:
0786: // If WID and HEI modifiers are unavailable add scale.
0787: if (!isFull) {
0788: postTransform.scale(dstROI.getWidth()
0789: / rendering.getWidth(), dstROI.getHeight()
0790: / rendering.getHeight());
0791: }
0792:
0793: // Translate (and scale) the result if necessary.
0794: if (!postTransform.isIdentity()) {
0795: Interpolation interp = Interpolation
0796: .getInstance(Interpolation.INTERP_NEAREST);
0797: RenderingHints hints = renderContext.getRenderingHints();
0798: if (hints != null
0799: && hints.containsKey(JAI.KEY_INTERPOLATION)) {
0800: interp = (Interpolation) hints
0801: .get(JAI.KEY_INTERPOLATION);
0802: }
0803: rendering = JAI.create("affine", rendering, postTransform,
0804: interp);
0805: }
0806:
0807: return rendering;
0808: }
0809:
0810: /**
0811: * Performs all operations on the client.
0812: */
0813: private RenderedImage clientProc(RenderContext renderContext,
0814: ParameterBlock paramBlock, int opMask, RenderedImage lowRes) {
0815: // Cache RenderContext components.
0816: AffineTransform at = renderContext.getTransform();
0817: RenderingHints hints = renderContext.getRenderingHints();
0818:
0819: ImagingListener listener = ImageUtil
0820: .getImagingListener(renderContext);
0821:
0822: // Obtain the number of levels and the size of the largest one.
0823: int[] maxSize = (int[]) lowRes.getProperty("max-size");
0824: int maxWidth = maxSize[0];
0825: int maxHeight = maxSize[1];
0826: int numLevels = ((Integer) lowRes
0827: .getProperty("resolution-number")).intValue();
0828:
0829: // Calculate the aspect ratios.
0830: float aspectRatioSource = (float) maxWidth / (float) maxHeight;
0831: float aspectRatio = (opMask & MASK_ASPECT_RATIO) != 0 ? paramBlock
0832: .getFloatParameter(7)
0833: : aspectRatioSource;
0834:
0835: // Determine the bounds of the destination image.
0836: Rectangle2D bounds2D = new Rectangle2D.Float(0.0F, 0.0F,
0837: aspectRatio, 1.0F);
0838:
0839: // Determine the dimensions of the rendered destination image.
0840: int width;
0841: int height;
0842: if (at.isIdentity()) { // Default rendering.
0843: AffineTransform afn = (AffineTransform) paramBlock
0844: .getObjectParameter(6);
0845: Rectangle2D bounds = afn.createTransformedShape(bounds2D)
0846: .getBounds2D();
0847: double H = maxHeight * bounds.getHeight();
0848: double W = maxHeight * bounds.getWidth();
0849: double m = Math.max(H, W / aspectRatioSource);
0850: height = (int) (m + 0.5);
0851: width = (int) (aspectRatioSource * m + 0.5);
0852: at = AffineTransform.getScaleInstance(width, height);
0853: renderContext = (RenderContext) renderContext.clone();
0854: renderContext.setTransform(at);
0855: } else {
0856: Rectangle bounds = at.createTransformedShape(bounds2D)
0857: .getBounds();
0858: width = bounds.width;
0859: height = bounds.height;
0860: }
0861:
0862: // Determine which resolution level of the IIP image to request.
0863: int res = numLevels - 1;
0864: int hRes = maxHeight;
0865: while (res > 0) {
0866: hRes = (int) ((hRes + 1.0F) / 2.0F); // get the next height
0867: if (hRes < height) { // stop if the next height is too small
0868: break;
0869: }
0870: res--;
0871: }
0872:
0873: // Create a RenderableImage from the selected resolution level.
0874: int[] subImageArray = (int[]) paramBlock.getObjectParameter(1);
0875: int subImage = subImageArray.length < res + 1 ? 0
0876: : subImageArray[res];
0877: if (subImage < 0) {
0878: subImage = 0;
0879: }
0880: ParameterBlock pb = new ParameterBlock();
0881: pb.add(paramBlock.getObjectParameter(0)).add(res).add(subImage);
0882: RenderedImage iipRes = JAI.create("iipresolution", pb);
0883: Vector sources = new Vector(1);
0884: sources.add(iipRes);
0885: RenderableImage ri = new MultiResolutionRenderableImage(
0886: sources, 0.0F, 0.0F, 1.0F);
0887:
0888: // Filtering.
0889: if ((opMask & MASK_FILTER) != 0) {
0890: float filter = paramBlock.getFloatParameter(2);
0891: pb = (new ParameterBlock()).addSource(ri).add(filter);
0892: ri = new RenderableImageOp(new FilterCRIF(), pb);
0893: }
0894:
0895: // Color-twist.
0896: // Cache the original number of bands in case the number of bands
0897: // changes due to addition of chroma and/or alpha channels in the
0898: // color-twist procedure.
0899: int nBands = iipRes.getSampleModel().getNumBands();
0900: if ((opMask & MASK_COLOR_TWIST) != 0) {
0901: double[][] ctw = getColorTwistMatrix(
0902: iipRes.getColorModel(), paramBlock);
0903: pb = (new ParameterBlock()).addSource(ri).add(ctw);
0904: ri = JAI.createRenderable("bandcombine", pb);
0905: nBands = ctw.length;
0906: }
0907:
0908: // Contrast.
0909: if ((opMask & MASK_CONTRAST) != 0) {
0910: int csType = iipRes.getColorModel().getColorSpace()
0911: .getType();
0912: boolean isPYCC = csType != ColorSpace.TYPE_GRAY
0913: && csType != ColorSpace.TYPE_RGB;
0914:
0915: if (isPYCC) {
0916: double[][] matrix;
0917: if (nBands == 3) { // PYCC
0918: matrix = composeMatrices(YCC_TO_RGB,
0919: YCC_TO_RGB_CONST);
0920: } else { // PYCC-A
0921: matrix = composeMatrices(YCCA_TO_RGBA,
0922: YCCA_TO_RGBA_CONST);
0923: }
0924: pb = (new ParameterBlock()).addSource(ri).add(matrix);
0925: ri = JAI.createRenderable("bandcombine", pb);
0926: }
0927:
0928: float contrast = paramBlock.getFloatParameter(4);
0929: LookupTableJAI lut = createContrastLUT(contrast, nBands);
0930:
0931: pb = (new ParameterBlock()).addSource(ri).add(lut);
0932: ri = JAI.createRenderable("lookup", pb);
0933:
0934: if (isPYCC) {
0935: double[][] matrix;
0936: if (nBands == 3) { // PYCC
0937: matrix = composeMatrices(RGB_TO_YCC,
0938: RGB_TO_YCC_CONST);
0939: } else { // PYCC-A
0940: matrix = composeMatrices(RGBA_TO_YCCA,
0941: RGBA_TO_YCCA_CONST);
0942: }
0943: pb = (new ParameterBlock()).addSource(ri).add(matrix);
0944: ri = JAI.createRenderable("bandcombine", pb);
0945: }
0946: }
0947:
0948: // Source rectangle of interest.
0949: if ((opMask & MASK_ROI_SOURCE) != 0) {
0950: // Get the source rectangle of interest.
0951: Rectangle2D rect = (Rectangle2D) paramBlock
0952: .getObjectParameter(5);
0953:
0954: // Check for intersection with source bounds.
0955: if (!rect.intersects(0.0, 0.0, aspectRatioSource, 1.0)) {
0956: throw new RuntimeException(JaiI18N
0957: .getString("IIPCRIF5"));
0958: }
0959:
0960: // Create the source rectangle.
0961: Rectangle2D rectS = new Rectangle2D.Float(0.0F, 0.0F,
0962: aspectRatioSource, 1.0F);
0963:
0964: // Crop out the desired region.
0965: if (!rect.equals(rectS)) {
0966: // Clip to the source bounds.
0967: rect = rect.createIntersection(rectS);
0968:
0969: // Crop to the clipped rectangle of interest.
0970: pb = (new ParameterBlock()).addSource(ri);
0971: pb.add((float) rect.getMinX()).add(
0972: (float) rect.getMinY());
0973: pb.add((float) rect.getWidth()).add(
0974: (float) rect.getHeight());
0975: ri = JAI.createRenderable("crop", pb);
0976:
0977: /* XXX
0978: // Embed the cropped image in an image the size of the source.
0979: pb = (new ParameterBlock()).addSource(ri);
0980: pb.add((float)rectS.getMinX()).add((float)rectS.getMinY());
0981: pb.add((float)rectS.getWidth()).add((float)rectS.getHeight());
0982: ri = JAI.createRenderable("crop", pb);
0983: */
0984: }
0985: }
0986:
0987: // Spatial orientation.
0988: if ((opMask & MASK_TRANSFORM) != 0) {
0989: AffineTransform afn = (AffineTransform) paramBlock
0990: .getObjectParameter(6);
0991: try {
0992: // The transform parameter is a backward mapping so invert it.
0993: afn = afn.createInverse();
0994: } catch (java.awt.geom.NoninvertibleTransformException e) {
0995: // This should never happen due to descriptor check.
0996: listener.errorOccurred(JaiI18N
0997: .getString("AffineNotInvertible"), e, this ,
0998: false);
0999:
1000: }
1001: pb = (new ParameterBlock()).addSource(ri).add(afn);
1002: if (hints != null
1003: && hints.containsKey(JAI.KEY_INTERPOLATION)) {
1004: pb.add(hints.get(JAI.KEY_INTERPOLATION));
1005: }
1006: ri = JAI.createRenderable("affine", pb);
1007: }
1008:
1009: // Destination rectangle of interest.
1010: // Set the destination rectangle of interest.
1011: Rectangle2D rgn = (opMask & MASK_ROI_DESTINATION) != 0 ? (Rectangle2D) paramBlock
1012: .getObjectParameter(8)
1013: : bounds2D;
1014:
1015: // Verify that the region is non-empty.
1016: if (rgn.isEmpty()) {
1017: throw new RuntimeException(JaiI18N.getString("IIPCRIF3"));
1018: }
1019:
1020: // Create a Rectangle2D for the current image.
1021: Rectangle2D riRect = new Rectangle2D.Float(
1022: (float) ri.getMinX(), (float) ri.getMinY(), (float) ri
1023: .getWidth(), (float) ri.getHeight());
1024:
1025: // If the current image bounds are not those of the requested
1026: // region then crop the image.
1027: if (!rgn.equals(riRect)) {
1028: // Intersect rgn with source image bounds.
1029: rgn = rgn.createIntersection(riRect);
1030:
1031: // Crop to the rectangle of interest.
1032: pb = (new ParameterBlock()).addSource(ri);
1033: pb.add((float) rgn.getMinX()).add((float) rgn.getMinY());
1034: pb.add((float) rgn.getWidth()).add((float) rgn.getHeight());
1035: ri = JAI.createRenderable("crop", pb);
1036: }
1037:
1038: // Return the rendering.
1039: return ri.createRendering(renderContext);
1040: }
1041:
1042: /**
1043: * Returns the default rendering of the RenderableImage produced by
1044: * the "iip" operation.
1045: */
1046: public RenderedImage create(ParameterBlock paramBlock,
1047: RenderingHints renderHints) {
1048: RenderableImage iipImage = JAI.createRenderable("iip",
1049: paramBlock);
1050:
1051: return iipImage.createDefaultRendering();
1052: }
1053:
1054: /**
1055: * Applies the specified set of operations to the IIP image
1056: * and returns a RenderedImage that satisfies the rendering context
1057: * provided.
1058: */
1059: public RenderedImage create(RenderContext renderContext,
1060: ParameterBlock paramBlock) {
1061: // Get the operation mask.
1062: int opMask = getOperationMask(paramBlock);
1063:
1064: ImagingListener listener = ImageUtil
1065: .getImagingListener(renderContext);
1066:
1067: // Get the lowest resolution level of the IIP image for property use.
1068: ParameterBlock pb = new ParameterBlock();
1069: int[] subImageArray = (int[]) paramBlock.getObjectParameter(1);
1070: pb.add(paramBlock.getObjectParameter(0)).add(0).add(
1071: subImageArray[0]);
1072: RenderedImage lowRes = JAI.create("iipresolution", pb);
1073:
1074: // Get the server capability mask.
1075: int serverMask = getServerCapabilityMask((String) paramBlock
1076: .getObjectParameter(0), lowRes);
1077:
1078: RenderedImage rendering = null;
1079:
1080: // Select the processing path based on the server's capabilities.
1081: if ((serverMask & SERVER_JPEG_FULL) == SERVER_JPEG_FULL
1082: || (serverMask & SERVER_FPX_FULL) == SERVER_FPX_FULL
1083: || (serverMask & SERVER_JPEG_PARTIAL) == SERVER_JPEG_PARTIAL
1084: || (serverMask & SERVER_FPX_PARTIAL) == SERVER_FPX_PARTIAL) {
1085: // All (FULL) or most (PARTIAL) ops on server
1086: rendering = serverProc(serverMask, renderContext,
1087: paramBlock, opMask, lowRes);
1088: } else {
1089: // All ops on client
1090: rendering = clientProc(renderContext, paramBlock, opMask,
1091: lowRes);
1092:
1093: // Do special processing if source rectangle of interest given.
1094: // The following approach works but is rather slow.
1095: if ((opMask & MASK_ROI_SOURCE) != 0) {
1096: // Retrieve the source rectangle of interest.
1097: Rectangle2D rgn = (Rectangle2D) paramBlock
1098: .getObjectParameter(5);
1099:
1100: // Retrieve a clone of the renderable transform.
1101: AffineTransform at = (AffineTransform) ((AffineTransform) (paramBlock
1102: .getObjectParameter(6))).clone();
1103:
1104: // If the transform is not the identity, invert it.
1105: if (!at.isIdentity()) {
1106: try {
1107: at = at.createInverse();
1108: } catch (Exception e) {
1109: String message = JaiI18N.getString("IIPCRIF6");
1110: listener.errorOccurred(message,
1111: new ImagingException(message, e), this ,
1112: false);
1113:
1114: // throw new RuntimeException(JaiI18N.getString("IIPCRIF6"));
1115: }
1116: }
1117:
1118: // Compose the inverted renderable transform with the
1119: // renderable-to-rendered transform to get the transform
1120: // from source renderable coordinates to destination
1121: // rendered coordinates.
1122: at.preConcatenate(renderContext.getTransform());
1123:
1124: // Create an ROI in destination rendered space.
1125: ROIShape roi = new ROIShape(at
1126: .createTransformedShape(rgn));
1127:
1128: // Create a TiledImage to contain the masked result.
1129: TiledImage ti = new TiledImage(rendering.getMinX(),
1130: rendering.getMinY(), rendering.getWidth(),
1131: rendering.getHeight(), rendering
1132: .getTileGridXOffset(), rendering
1133: .getTileGridYOffset(), rendering
1134: .getSampleModel(), rendering
1135: .getColorModel());
1136:
1137: // Set the TiledImage data source to the rendering.
1138: ti.set(rendering, roi);
1139:
1140: // Create a constant-valued image for the background.
1141: pb = new ParameterBlock();
1142: pb.add((float) ti.getWidth());
1143: pb.add((float) ti.getHeight());
1144: Byte[] bandValues = new Byte[ti.getSampleModel()
1145: .getNumBands()];
1146: for (int b = 0; b < bandValues.length; b++) {
1147: bandValues[b] = new Byte((byte) 255);
1148: }
1149: pb.add(bandValues);
1150:
1151: ImageLayout il = new ImageLayout();
1152: il.setSampleModel(ti.getSampleModel());
1153: RenderingHints rh = new RenderingHints(
1154: JAI.KEY_IMAGE_LAYOUT, il);
1155:
1156: PlanarImage constImage = JAI.create("constant", pb, rh);
1157:
1158: // Compute a complement ROI.
1159: ROI complementROI = (new ROIShape(ti.getBounds()))
1160: .subtract(roi);
1161: ;
1162:
1163: // Fill the background.
1164: int maxTileY = ti.getMaxTileY();
1165: int maxTileX = ti.getMaxTileX();
1166: for (int j = ti.getMinTileY(); j <= maxTileY; j++) {
1167: for (int i = ti.getMinTileX(); i <= maxTileX; i++) {
1168: if (!roi.intersects(ti.getTileRect(i, j))) {
1169: ti.setData(constImage.getTile(i, j),
1170: complementROI);
1171: }
1172: }
1173: }
1174:
1175: // Set the rendering to the TiledImage.
1176: rendering = ti;
1177: }
1178: }
1179:
1180: // If the server supports only the first tier of composed image
1181: // command modifiers or none at all then the "RFM" modifier
1182: // effect must be replicated on the client if this would be
1183: // required by the supplied parameters.
1184: if ((serverMask & SERVER_JPEG_FULL) != SERVER_JPEG_FULL
1185: && (serverMask & SERVER_FPX_FULL) != SERVER_FPX_FULL) {
1186: if ((opMask & MASK_ROTATION) != 0) {
1187: // NOTE: The transpose operation uses clockwise rotation
1188: // whereas this operation expects counterclockwise.
1189: EnumeratedParameter transposeType = null;
1190: switch (paramBlock.getIntParameter(9)) {
1191: case 90:
1192: transposeType = TransposeDescriptor.ROTATE_270;
1193: break;
1194: case 180:
1195: transposeType = TransposeDescriptor.ROTATE_180;
1196: break;
1197: case 270:
1198: transposeType = TransposeDescriptor.ROTATE_90;
1199: break;
1200: }
1201: if (transposeType != null) { // deliberately redundant test
1202: rendering = JAI.create("transpose", rendering,
1203: transposeType);
1204: }
1205: }
1206:
1207: if ((opMask & MASK_MIRROR_AXIS) != 0) {
1208: String axis = (String) paramBlock
1209: .getObjectParameter(10);
1210: EnumeratedParameter transposeType = axis
1211: .equalsIgnoreCase("x") ? TransposeDescriptor.FLIP_VERTICAL
1212: : TransposeDescriptor.FLIP_HORIZONTAL;
1213: rendering = JAI.create("transpose", rendering,
1214: transposeType);
1215: }
1216: }
1217:
1218: return rendering;
1219: }
1220:
1221: /**
1222: * Returns the bounds of the RenderableImage. This will be the
1223: * rendering-independent destination rectangle of interest if supplied
1224: * or the rendering-independent destination image bounds if not.
1225: */
1226: public Rectangle2D getBounds2D(ParameterBlock paramBlock) {
1227: int opMask = getOperationMask(paramBlock);
1228:
1229: if ((opMask & MASK_ROI_DESTINATION) != 0) {
1230: return (Rectangle2D) paramBlock.getObjectParameter(8);
1231: }
1232:
1233: float aspectRatioDestination;
1234: if ((opMask & MASK_ASPECT_RATIO) != 0) {
1235: aspectRatioDestination = paramBlock.getFloatParameter(7);
1236: } else {
1237: // Get the lowest resolution level of the IIP image.
1238: ParameterBlock pb = new ParameterBlock();
1239: int[] subImageArray = (int[]) paramBlock
1240: .getObjectParameter(1);
1241: pb.add(paramBlock.getObjectParameter(0));
1242: pb.add(0).add(subImageArray[0]);
1243: RenderedImage lowRes = JAI.create("iipresolution", pb);
1244:
1245: int[] maxSize = (int[]) lowRes.getProperty("max-size");
1246:
1247: aspectRatioDestination = (float) maxSize[0]
1248: / (float) maxSize[1];
1249: }
1250:
1251: return new Rectangle2D.Float(0.0F, 0.0F,
1252: aspectRatioDestination, 1.0F);
1253: }
1254:
1255: public static void main(String[] args) {
1256: int nr = 0;
1257: int nc = 0;
1258:
1259: double[][] x = matrixMultiply(RGBA_TO_YCCA, YCCA_TO_RGBA);
1260: nr = x.length;
1261: nc = x[0].length;
1262: for (int r = 0; r < nr; r++) {
1263: for (int c = 0; c < nc; c++) {
1264: System.out.print(x[r][c] + " ");
1265: }
1266: System.out.println("");
1267: }
1268: System.out.println("");
1269:
1270: x = matrixMultiply(RGB_TO_YCC, YCC_TO_RGB);
1271: nr = x.length;
1272: nc = x[0].length;
1273: for (int r = 0; r < nr; r++) {
1274: for (int c = 0; c < nc; c++) {
1275: System.out.print(x[r][c] + " ");
1276: }
1277: System.out.println("");
1278: }
1279: System.out.println("");
1280:
1281: double[][] b = new double[][] { { 1.0 }, { 2.0 }, { 3.0 },
1282: { 4.0 } };
1283: double[][] A = composeMatrices(YCCA_TO_RGBA, b);
1284: nr = A.length;
1285: nc = A[0].length;
1286: for (int r = 0; r < nr; r++) {
1287: for (int c = 0; c < nc; c++) {
1288: System.out.print(A[r][c] + " ");
1289: }
1290: System.out.println("");
1291: }
1292: System.out.println("");
1293:
1294: double[][] d4 = matrixMultiply(RGBA_TO_YCCA, YCCA_TO_RGBA_CONST);
1295: nr = d4.length;
1296: nc = d4[0].length;
1297: for (int r = 0; r < nr; r++) {
1298: for (int c = 0; c < nc; c++) {
1299: System.out.print(-d4[r][c] + " ");
1300: }
1301: System.out.println("");
1302: }
1303: System.out.println("");
1304:
1305: double[][] d3 = matrixMultiply(RGB_TO_YCC, YCC_TO_RGB_CONST);
1306: nr = d3.length;
1307: nc = d3[0].length;
1308: for (int r = 0; r < nr; r++) {
1309: for (int c = 0; c < nc; c++) {
1310: System.out.print(-d3[r][c] + " ");
1311: }
1312: System.out.println("");
1313: }
1314: System.out.println("");
1315: }
1316: }
|