/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Stephan Wahlbrink - fix for bug 341702 - incorrect mixing of images with alpha channel *******************************************************************************/ package org.eclipse.jface.resource; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; /** * Abstract base class for image descriptors that synthesize an image from other * images in order to simulate the effect of custom drawing. For example, this * could be used to superimpose a red bar dexter symbol across an image to * indicate that something was disallowed. * <p> * Subclasses must implement the <code>getSize</code> and <code>fill</code> * methods. Little or no work happens until the image descriptor's image is * actually requested by a call to <code>createImage</code> (or to * <code>getImageData</code> directly). * </p> */ public abstract class CompositeImageDescriptor extends ImageDescriptor { /** * The image data for this composite image. */ private ImageData imageData; /** * Constructs an uninitialized composite image. */ protected CompositeImageDescriptor() { } /** * Draw the composite images. * <p> * Subclasses must implement this framework method to paint images within * the given bounds using one or more calls to the <code>drawImage</code> * framework method. * </p> * * @param width * the width * @param height * the height */ protected abstract void drawCompositeImage(int width, int height); /** * Draws the given source image data into this composite image at the given * position. * <p> * Call this internal framework method to superimpose another image atop * this composite image. * </p> * * @param src * the source image data * @param ox * the x position * @param oy * the y position */ final protected void drawImage(ImageData src, int ox, int oy) { ImageData dst = imageData; PaletteData srcPalette = src.palette; ImageData srcMask = null; int alphaMask = 0, alphaShift = 0; if (src.maskData != null) { srcMask = src.getTransparencyMask (); if (src.depth == 32) { alphaMask = ~(srcPalette.redMask | srcPalette.greenMask | srcPalette.blueMask); while (alphaMask != 0 && ((alphaMask >>> alphaShift) & 1) == 0) alphaShift++; } } for (int srcY = 0, dstY = srcY + oy; srcY < src.height; srcY++, dstY++) { for (int srcX = 0, dstX = srcX + ox; srcX < src.width; srcX++, dstX++) { if (!(0 <= dstX && dstX < dst.width && 0 <= dstY && dstY < dst.height)) continue; int srcPixel = src.getPixel(srcX, srcY); int srcAlpha = 255; if (src.maskData != null) { if (src.depth == 32) { srcAlpha = (srcPixel & alphaMask) >>> alphaShift; if (srcAlpha == 0) { srcAlpha = srcMask.getPixel(srcX, srcY) != 0 ? 255 : 0; } } else { if (srcMask.getPixel(srcX, srcY) == 0) srcAlpha = 0; } } else if (src.transparentPixel != -1) { if (src.transparentPixel == srcPixel) srcAlpha = 0; } else if (src.alpha != -1) { srcAlpha = src.alpha; } else if (src.alphaData != null) { srcAlpha = src.getAlpha(srcX, srcY); } if (srcAlpha == 0) continue; int srcRed, srcGreen, srcBlue; if (srcPalette.isDirect) { srcRed = srcPixel & srcPalette.redMask; srcRed = (srcPalette.redShift < 0) ? srcRed >>> -srcPalette.redShift : srcRed << srcPalette.redShift; srcGreen = srcPixel & srcPalette.greenMask; srcGreen = (srcPalette.greenShift < 0) ? srcGreen >>> -srcPalette.greenShift : srcGreen << srcPalette.greenShift; srcBlue = srcPixel & srcPalette.blueMask; srcBlue = (srcPalette.blueShift < 0) ? srcBlue >>> -srcPalette.blueShift : srcBlue << srcPalette.blueShift; } else { RGB rgb = srcPalette.getRGB(srcPixel); srcRed = rgb.red; srcGreen = rgb.green; srcBlue = rgb.blue; } int dstRed, dstGreen, dstBlue, dstAlpha; if (srcAlpha == 255) { dstRed = srcRed; dstGreen = srcGreen; dstBlue= srcBlue; dstAlpha = srcAlpha; } else { int dstPixel = dst.getPixel(dstX, dstY); dstAlpha = dst.getAlpha(dstX, dstY); dstRed = (dstPixel & 0xFF) >>> 0; dstGreen = (dstPixel & 0xFF00) >>> 8; dstBlue = (dstPixel & 0xFF0000) >>> 16; if (dstAlpha == 255) { // simplified calculations for performance dstRed += (srcRed - dstRed) * srcAlpha / 255; dstGreen += (srcGreen - dstGreen) * srcAlpha / 255; dstBlue += (srcBlue - dstBlue) * srcAlpha / 255; } else { // See Porter T., Duff T. 1984. "Compositing Digital Images". // Computer Graphics 18 (3): 253-259. dstRed = srcRed * srcAlpha * 255 + dstRed * dstAlpha * (255 - srcAlpha); dstGreen = srcGreen * srcAlpha * 255 + dstGreen * dstAlpha * (255 - srcAlpha); dstBlue = srcBlue * srcAlpha * 255 + dstBlue * dstAlpha * (255 - srcAlpha); dstAlpha = srcAlpha * 255 + dstAlpha * (255 - srcAlpha); if (dstAlpha != 0) { // if both original alphas == 0, then all colors are 0 dstRed /= dstAlpha; dstGreen /= dstAlpha; dstBlue /= dstAlpha; dstAlpha /= 255; } } } dst.setPixel(dstX, dstY, ((dstRed & 0xFF) << 0) | ((dstGreen & 0xFF) << 8) | ((dstBlue & 0xFF) << 16)); dst.setAlpha(dstX, dstY, dstAlpha); } } } /* * (non-Javadoc) Method declared on ImageDesciptor. */ public ImageData getImageData() { Point size = getSize(); /* Create a 24 bit image data with alpha channel */ imageData = new ImageData(size.x, size.y, 24, new PaletteData(0xFF, 0xFF00, 0xFF0000)); imageData.alphaData = new byte[imageData.width * imageData.height]; drawCompositeImage(size.x, size.y); /* Detect minimum transparency */ boolean transparency = false; byte[] alphaData = imageData.alphaData; for (int i = 0; i < alphaData.length; i++) { int alpha = alphaData[i] & 0xFF; if (!(alpha == 0 || alpha == 255)) { /* Full alpha channel transparency */ return imageData; } if (!transparency && alpha == 0) transparency = true; } if (transparency) { /* Reduce to 1-bit alpha channel transparency */ PaletteData palette = new PaletteData(new RGB[]{new RGB(0, 0, 0), new RGB(255, 255, 255)}); ImageData mask = new ImageData(imageData.width, imageData.height, 1, palette); for (int y = 0; y < mask.height; y++) { for (int x = 0; x < mask.width; x++) { mask.setPixel(x, y, imageData.getAlpha(x, y) == 255 ? 1 : 0); } } } else { /* no transparency */ imageData.alphaData = null; } return imageData; } /** * Return the transparent pixel for the receiver. * <strong>NOTE</strong> This value is not currently in use in the * default implementation. * @return int * @since 3.3 */ protected int getTransparentPixel() { return 0; } /** * Return the size of this composite image. * <p> * Subclasses must implement this framework method. * </p> * * @return the x and y size of the image expressed as a point object */ protected abstract Point getSize(); /** * @param imageData The imageData to set. * @since 3.3 */ protected void setImageData(ImageData imageData) { this.imageData = imageData; } }