/* * omeis.providers.re.RenderHSBWaveTask * * Copyright 2006 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package omeis.providers.re; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ome.util.PixelData; import omeis.providers.re.codomain.CodomainChain; import omeis.providers.re.data.Plane2D; import omeis.providers.re.quantum.BinaryMaskQuantizer; import omeis.providers.re.quantum.QuantizationException; import omeis.providers.re.quantum.QuantumStrategy; /** * A task object to render an image region asynchronously. This task is used by * the {@link HSBStrategy} to do concurrent rendering if more than one region * has to be processed. * * @author Chris Allan      <a * href="mailto:callan@blackcat.ca">callan@blackat.ca</a> * @version 3.0 <small> (<b>Internal version:</b> $Revision$ $Date: * 2005/06/17 12:57:33 $) </small> * @since OMERO3.0 */ class RenderHSBRegionTask implements RenderingTask { /** The logger for this particular class */ private static Logger log = LoggerFactory.getLogger(RenderHSBRegionTask.class); /** Buffer to hold the output image's data. */ private RGBBuffer dataBuffer; /** The wavelength data. */ private List<Plane2D> wData; /** How to quantize a pixel intensity value. */ private List<QuantumStrategy> strategies; /** The spatial transformations to apply to the quantized data. */ private CodomainChain cc; /** * The color components used when mapping a quantized value onto the color * space. */ private List<int[]> colors; /** The <i>X1</i>-axis start */ private int x1Start; /** The <i>X1</i>-axis end */ private int x1End; /** The <i>X2</i>-axis start */ private int x2Start; /** The <i>X2</i>-axis end */ private int x2End; /** The optimizations that the renderer has turned on for us. */ private Optimizations optimizations; /** * Creates a new instance to render a wavelength. * * @param dataBuffer * Buffer to hold the output image's data. * @param wData * The wavelength data. * @param strategies * The quantum strategy for each wavelength. * @param cc * The spatial transformations to apply to the quantized data. * @param colors * The color components to use when mapping quantized values onto * the color space. * @param x1Start * The <i>X1</i>-axis start * @param x1End * The <i>X1</i>-axis start * @param x2Start * The <i>X2</i>-axis start * @param x2End * The <i>X2</i>-axis start */ RenderHSBRegionTask(RGBBuffer dataBuffer, List<Plane2D> wData, List<QuantumStrategy> strategies, CodomainChain cc, List<int[]> colors, Optimizations optimizations, int x1Start, int x1End, int x2Start, int x2End) { this.dataBuffer = dataBuffer; this.wData = wData; this.strategies = strategies; this.cc = cc; this.colors = colors; this.optimizations = optimizations; this.x1Start = x1Start; this.x1End = x1End; this.x2Start = x2Start; this.x2End = x2End; } /** * Renders the region. * * @throws QuantizationException * If an error occurs while quantizing a pixels intensity value. */ public Object call() throws QuantizationException { log.debug("Buffer type: "+dataBuffer); if (dataBuffer instanceof RGBIntBuffer) { renderPackedInt(); } else if (dataBuffer instanceof RGBAIntBuffer){ renderPackedIntAsRGBA(); } else { //renderPackedInt(); renderBanded(); // cf. ticket #1646 } return null; } /** * Renders into a banded byte buffer. * * @throws QuantizationException * if there is an error during pixel value quantization. */ private void renderBanded() throws QuantizationException { int discreteValue, pix; int rValue, gValue, bValue; float v; int width = x1End - x1Start; int i = 0; byte[] r = dataBuffer.getRedBand(); byte[] g = dataBuffer.getGreenBand(); byte[] b = dataBuffer.getBlueBand(); for (Plane2D plane : wData) { int[] color = colors.get(i); QuantumStrategy qs = strategies.get(i); int rColor = color[ColorsFactory.RED_INDEX]; int gColor = color[ColorsFactory.GREEN_INDEX]; int bColor = color[ColorsFactory.BLUE_INDEX]; float alpha = new Float( color[ColorsFactory.ALPHA_INDEX]).floatValue() / 65025;// 255*255 for (int x2 = x2Start; x2 < x2End; ++x2) { for (int x1 = x1Start; x1 < x1End; ++x1) { pix = width * x2 + x1; discreteValue = qs.quantize(plane.getPixelValue(x1, x2)); discreteValue = cc.transform(discreteValue); // Pre-multiply the alpha component and add the existing // colour value to the new colour value. v = discreteValue * alpha; rValue = (int) (rColor * v) + r[pix]; gValue = (int) (gColor * v) + g[pix]; bValue = (int) (bColor * v) + b[pix]; // Ensure that each colour component value is between 0 and // 255 (byte). We must make *certain* that values to not // wrap over 255 otherwise there will be corruption // introduced into the rendered image. if (rValue > 255) { rValue = 255; } if (gValue > 255) { gValue = 255; } if (bValue > 255) { bValue = 255; } r[pix] = (byte) (rValue & 0xFF); g[pix] = (byte) (gValue & 0xFF); b[pix] = (byte) (bValue & 0xFF); } } i++; } } /** * Renders into a packed integer array. * * @throws QuantizationException * if there is an error during pixel value quantization. */ private void renderPackedInt() throws QuantizationException { int discreteValue, pix; double redRatio, greenRatio, blueRatio; int rValue, gValue, bValue; int newRValue, newGValue, newBValue; int colorOffset = 24; // Only used when we're doing primary color. int width = x1End - x1Start; int i = 0; int[] buf = ((RGBIntBuffer) dataBuffer).getDataBuffer(); boolean isPrimaryColor = optimizations.isPrimaryColorEnabled(); boolean isAlphaless = optimizations.isAlphalessRendering(); for (Plane2D plane : wData) { int[] color = colors.get(i); QuantumStrategy qs = strategies.get(i); boolean isMask = qs instanceof BinaryMaskQuantizer? true : false; redRatio = color[ColorsFactory.RED_INDEX] > 0 ? color[ColorsFactory.RED_INDEX] / 255.0 : 0.0; greenRatio = color[ColorsFactory.GREEN_INDEX] > 0 ? color[ColorsFactory.GREEN_INDEX] / 255.0 : 0.0; blueRatio = color[ColorsFactory.BLUE_INDEX] > 0 ? color[ColorsFactory.BLUE_INDEX] / 255.0 : 0.0; boolean isXYPlanar = plane.isXYPlanar(); PixelData data = plane.getData(); int bytesPerPixel = data.bytesPerPixel(); // Get our color offset if we've got the primary color optimization // enabled. if (isPrimaryColor) colorOffset = getColorOffset(color); float alpha = new Integer( color[ColorsFactory.ALPHA_INDEX]).floatValue() / 255; for (int x2 = x2Start; x2 < x2End; ++x2) { for (int x1 = x1Start; x1 < x1End; ++x1) { pix = width * x2 + x1; if (isXYPlanar) discreteValue = qs.quantize( data.getPixelValueDirect(pix * bytesPerPixel)); else discreteValue = qs.quantize(plane.getPixelValue(x1, x2)); // Right now we have no transforms being used so it's safe to // comment this out for the time being. //discreteValue = cc.transform(discreteValue); // Primary colour optimization is in effect, we don't need // to do any of the sillyness below just shift the value // into the correct colour component slot and move on to // the next pixel value. if (colorOffset != 24) { buf[pix] |= 0xFF000000; // Alpha. buf[pix] |= discreteValue << colorOffset; continue; } newRValue = (int) (redRatio * discreteValue); newGValue = (int) (greenRatio * discreteValue); newBValue = (int) (blueRatio * discreteValue); // Pre-multiply the alpha for each colour component if the // image has a non-1.0 alpha component. if (!isAlphaless) { newRValue *= alpha; newGValue *= alpha; newBValue *= alpha; } if (isMask && discreteValue == 255) { // Since the mask is a hard value, we do not want to // compromise on colour fidelity. Packed each colour // component along with a 1.0 alpha into the buffer so // that buffered images that use this buffer can be // type 1 (3 bands, pre-multiplied alpha) or type 2 // (4 bands, alpha component included). buf[pix] = 0xFF000000 | newRValue << 16 | newGValue << 8 | newBValue; continue; } // Add the existing colour component values to the new // colour component values. rValue = ((buf[pix] & 0x00FF0000) >> 16) + newRValue; gValue = ((buf[pix] & 0x0000FF00) >> 8) + newGValue; bValue = (buf[pix] & 0x000000FF) + newBValue; // Ensure that each colour component value is between 0 and // 255 (byte). We must make *certain* that values do not // wrap over 255 otherwise there will be corruption // introduced into the rendered image. The value may be over // 255 if we have mapped two high intensity channels to // the same color. if (rValue > 255) { rValue = 255; } if (gValue > 255) { gValue = 255; } if (bValue > 255) { bValue = 255; } // Packed each colour component along with a 1.0 alpha into // the buffer so that buffered images that use this buffer // can be type 1 (3 bands, pre-multiplied alpha) or type 2 // (4 bands, alpha component included). buf[pix] = 0xFF000000 | rValue << 16 | gValue << 8 | bValue; } } i++; } } /** * Renders into a packed integer array. * * @throws QuantizationException * if there is an error during pixel value quantization. */ private void renderPackedIntAsRGBA() throws QuantizationException { int discreteValue, pix; double redRatio, greenRatio, blueRatio; int rValue, gValue, bValue; int newRValue, newGValue, newBValue; int colorOffset = 32; // Only used when we're doing primary color. int width = x1End - x1Start; int i = 0; int[] buf = ((RGBAIntBuffer) dataBuffer).getDataBuffer(); boolean isPrimaryColor = optimizations.isPrimaryColorEnabled(); boolean isAlphaless = optimizations.isAlphalessRendering(); for (Plane2D plane : wData) { int[] color = colors.get(i); QuantumStrategy qs = strategies.get(i); redRatio = color[ColorsFactory.RED_INDEX] > 0 ? color[ColorsFactory.RED_INDEX] / 255.0 : 0.0; greenRatio = color[ColorsFactory.GREEN_INDEX] > 0 ? color[ColorsFactory.GREEN_INDEX] / 255.0 : 0.0; blueRatio = color[ColorsFactory.BLUE_INDEX] > 0 ? color[ColorsFactory.BLUE_INDEX] / 255.0 : 0.0; boolean isXYPlanar = plane.isXYPlanar(); PixelData data = plane.getData(); int bytesPerPixel = data.bytesPerPixel(); // Get our color offset if we've got the primary color optimization // enabled. if (isPrimaryColor) colorOffset = getColorOffsetAsRGBA(color); float alpha = new Integer(color[ColorsFactory.ALPHA_INDEX]).floatValue() / 255; for (int x2 = x2Start; x2 < x2End; ++x2) { for (int x1 = x1Start; x1 < x1End; ++x1) { pix = width * x2 + x1; if (isXYPlanar) discreteValue = qs.quantize( data.getPixelValueDirect(pix * bytesPerPixel)); else discreteValue = qs.quantize(plane.getPixelValue(x1, x2)); // Right now we have no transforms being used so it's safe to // comment this out for the time being. //discreteValue = cc.transform(discreteValue); // Primary colour optimization is in effect, we don't need // to do any of the sillyness below just shift the value // into the correct colour component slot and move on to // the next pixel value. if (colorOffset != 32) { buf[pix] |= 0x000000FF; // Alpha. buf[pix] |= discreteValue << colorOffset; continue; } newRValue = (int) (redRatio * discreteValue); newGValue = (int) (greenRatio * discreteValue); newBValue = (int) (blueRatio * discreteValue); // Pre-multiply the alpha for each colour component if the // image has a non-1.0 alpha component. if (!isAlphaless) { newRValue *= alpha; newGValue *= alpha; newBValue *= alpha; } // Add the existing colour component values to the new // colour component values. rValue = ((buf[pix] & 0xFF000000) >> 24) + newRValue; gValue = ((buf[pix] & 0x00FF0000) >> 16) + newGValue; bValue = ((buf[pix] & 0x0000FF00) >>8) + newBValue; // Ensure that each colour component value is between 0 and // 255 (byte). We must make *certain* that values do not // wrap over 255 otherwise there will be corruption // introduced into the rendered image. The value may be over // 255 if we have mapped two high intensity channels to // the same color. if (rValue > 255) { rValue = 255; } if (gValue > 255) { gValue = 255; } if (bValue > 255) { bValue = 255; } // Packed each colour component along with a 1.0 alpha into // the buffer so that buffered images that use this buffer // can be type 1 (3 bands, pre-multiplied alpha) or type 2 // (4 bands, alpha component included). buf[pix] = 0x000000FF | rValue << 24 | gValue << 16 | bValue<<8; } } i++; } } /** * Returns a color offset based on which color component is 0xFF. * @param color the color to check. * @return an integer color offset in bits. */ private int getColorOffset(int[] color) { if (color[ColorsFactory.RED_INDEX] == 255) return 16; if (color[ColorsFactory.GREEN_INDEX] == 255) return 8; if (color[ColorsFactory.BLUE_INDEX] == 255) return 0; throw new IllegalArgumentException( "Unable to find color component offset in color."); } /** * Returns a color offset based on which color component is 0xFF. * @param color the color to check. This is for colour components * of RGBA, rather than java colour components which are ARGB. * @return an integer color offset in bits. */ private int getColorOffsetAsRGBA(int[] color) { if (color[ColorsFactory.RED_INDEX] == 255) return 24; if (color[ColorsFactory.GREEN_INDEX] == 255) return 16; if (color[ColorsFactory.BLUE_INDEX] == 255) return 8; throw new IllegalArgumentException( "Unable to find color component offset in color."); } }