| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.ide.eclipse.adt.internal.editors.layout.gle2; |
| |
| import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE; |
| |
| import com.android.ide.common.api.Rect; |
| import com.android.ide.eclipse.adt.internal.editors.IconFactory; |
| |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.graphics.Device; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontMetrics; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.ImageData; |
| import org.eclipse.swt.graphics.PaletteData; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| |
| import java.awt.Graphics; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.DataBuffer; |
| import java.awt.image.DataBufferByte; |
| import java.awt.image.DataBufferInt; |
| import java.awt.image.WritableRaster; |
| import java.util.List; |
| |
| /** |
| * Various generic SWT utilities such as image conversion. |
| */ |
| public class SwtUtils { |
| |
| private SwtUtils() { |
| } |
| |
| /** |
| * Returns the {@link PaletteData} describing the ARGB ordering expected from integers |
| * representing pixels for AWT {@link BufferedImage}. |
| * |
| * @param imageType the {@link BufferedImage#getType()} type |
| * @return A new {@link PaletteData} suitable for AWT images. |
| */ |
| public static PaletteData getAwtPaletteData(int imageType) { |
| switch (imageType) { |
| case BufferedImage.TYPE_INT_RGB: |
| case BufferedImage.TYPE_INT_ARGB: |
| case BufferedImage.TYPE_INT_ARGB_PRE: |
| return new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF); |
| |
| case BufferedImage.TYPE_3BYTE_BGR: |
| case BufferedImage.TYPE_4BYTE_ABGR: |
| case BufferedImage.TYPE_4BYTE_ABGR_PRE: |
| return new PaletteData(0x000000FF, 0x0000FF00, 0x00FF0000); |
| |
| default: |
| throw new UnsupportedOperationException("RGB type not supported yet."); |
| } |
| } |
| |
| /** |
| * Returns true if the given type of {@link BufferedImage} is supported for |
| * conversion. For unsupported formats, use |
| * {@link #convertToCompatibleFormat(BufferedImage)} first. |
| * |
| * @param imageType the {@link BufferedImage#getType()} |
| * @return true if we can convert the given buffered image format |
| */ |
| private static boolean isSupportedPaletteType(int imageType) { |
| switch (imageType) { |
| case BufferedImage.TYPE_INT_RGB: |
| case BufferedImage.TYPE_INT_ARGB: |
| case BufferedImage.TYPE_INT_ARGB_PRE: |
| case BufferedImage.TYPE_3BYTE_BGR: |
| case BufferedImage.TYPE_4BYTE_ABGR: |
| case BufferedImage.TYPE_4BYTE_ABGR_PRE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** Converts the given arbitrary {@link BufferedImage} to another {@link BufferedImage} |
| * in a format that is supported (see {@link #isSupportedPaletteType(int)}) |
| * |
| * @param image the image to be converted |
| * @return a new image that is in a guaranteed compatible format |
| */ |
| private static BufferedImage convertToCompatibleFormat(BufferedImage image) { |
| BufferedImage converted = new BufferedImage(image.getWidth(), image.getHeight(), |
| BufferedImage.TYPE_INT_ARGB); |
| Graphics graphics = converted.getGraphics(); |
| graphics.drawImage(image, 0, 0, null); |
| graphics.dispose(); |
| |
| return converted; |
| } |
| |
| /** |
| * Converts an AWT {@link BufferedImage} into an equivalent SWT {@link Image}. Whether |
| * the transparency data is transferred is optional, and this method can also apply an |
| * alpha adjustment during the conversion. |
| * <p/> |
| * Implementation details: on Windows, the returned {@link Image} will have an ordering |
| * matching the Windows DIB (e.g. RGBA, not ARGB). Callers must make sure to use |
| * <code>Image.getImageData().paletteData</code> to get the right pixels out of the image. |
| * |
| * @param display The display where the SWT image will be shown |
| * @param awtImage The AWT {@link BufferedImage} |
| * @param transferAlpha If true, copy alpha data out of the source image |
| * @param globalAlpha If -1, do nothing, otherwise adjust the alpha of the final image |
| * by the given amount in the range [0,255] |
| * @return A new SWT {@link Image} with the same contents as the source |
| * {@link BufferedImage} |
| */ |
| public static Image convertToSwt(Device display, BufferedImage awtImage, |
| boolean transferAlpha, int globalAlpha) { |
| if (!isSupportedPaletteType(awtImage.getType())) { |
| awtImage = convertToCompatibleFormat(awtImage); |
| } |
| |
| int width = awtImage.getWidth(); |
| int height = awtImage.getHeight(); |
| |
| WritableRaster raster = awtImage.getRaster(); |
| DataBuffer dataBuffer = raster.getDataBuffer(); |
| ImageData imageData = |
| new ImageData(width, height, 32, getAwtPaletteData(awtImage.getType())); |
| |
| if (dataBuffer instanceof DataBufferInt) { |
| int[] imageDataBuffer = ((DataBufferInt) dataBuffer).getData(); |
| imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); |
| } else if (dataBuffer instanceof DataBufferByte) { |
| byte[] imageDataBuffer = ((DataBufferByte) dataBuffer).getData(); |
| try { |
| imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); |
| } catch (SWTException se) { |
| // Unsupported depth |
| return convertToSwt(display, convertToCompatibleFormat(awtImage), |
| transferAlpha, globalAlpha); |
| } |
| } |
| |
| if (transferAlpha) { |
| byte[] alphaData = new byte[height * width]; |
| for (int y = 0; y < height; y++) { |
| byte[] alphaRow = new byte[width]; |
| for (int x = 0; x < width; x++) { |
| int alpha = awtImage.getRGB(x, y) >>> 24; |
| |
| // We have to multiply in the alpha now since if we |
| // set ImageData.alpha, it will ignore the alphaData. |
| if (globalAlpha != -1) { |
| alpha = alpha * globalAlpha >> 8; |
| } |
| |
| alphaRow[x] = (byte) alpha; |
| } |
| System.arraycopy(alphaRow, 0, alphaData, y * width, width); |
| } |
| |
| imageData.alphaData = alphaData; |
| } else if (globalAlpha != -1) { |
| imageData.alpha = globalAlpha; |
| } |
| |
| return new Image(display, imageData); |
| } |
| |
| /** |
| * Converts a direct-color model SWT image to an equivalent AWT image. If the image |
| * does not have a supported color model, returns null. This method does <b>NOT</b> |
| * preserve alpha in the source image. |
| * |
| * @param swtImage the SWT image to be converted to AWT |
| * @return an AWT image representing the source SWT image |
| */ |
| public static BufferedImage convertToAwt(Image swtImage) { |
| ImageData swtData = swtImage.getImageData(); |
| BufferedImage awtImage = |
| new BufferedImage(swtData.width, swtData.height, BufferedImage.TYPE_INT_ARGB); |
| PaletteData swtPalette = swtData.palette; |
| if (swtPalette.isDirect) { |
| PaletteData awtPalette = getAwtPaletteData(awtImage.getType()); |
| |
| if (swtPalette.equals(awtPalette)) { |
| // No color conversion needed. |
| for (int y = 0; y < swtData.height; y++) { |
| for (int x = 0; x < swtData.width; x++) { |
| int pixel = swtData.getPixel(x, y); |
| awtImage.setRGB(x, y, 0xFF000000 | pixel); |
| } |
| } |
| } else { |
| // We need to remap the colors |
| int sr = -awtPalette.redShift + swtPalette.redShift; |
| int sg = -awtPalette.greenShift + swtPalette.greenShift; |
| int sb = -awtPalette.blueShift + swtPalette.blueShift; |
| |
| for (int y = 0; y < swtData.height; y++) { |
| for (int x = 0; x < swtData.width; x++) { |
| int pixel = swtData.getPixel(x, y); |
| |
| int r = pixel & swtPalette.redMask; |
| int g = pixel & swtPalette.greenMask; |
| int b = pixel & swtPalette.blueMask; |
| r = (sr < 0) ? r >>> -sr : r << sr; |
| g = (sg < 0) ? g >>> -sg : g << sg; |
| b = (sb < 0) ? b >>> -sb : b << sb; |
| |
| pixel = 0xFF000000 | r | g | b; |
| awtImage.setRGB(x, y, pixel); |
| } |
| } |
| } |
| } else { |
| return null; |
| } |
| |
| return awtImage; |
| } |
| |
| /** |
| * Creates a new image from a source image where the contents from a given set of |
| * bounding boxes are copied into the new image and the rest is left transparent. A |
| * scale can be applied to make the resulting image larger or smaller than the source |
| * image. Note that the alpha channel in the original image is ignored, and the alpha |
| * values for the painted rectangles will be set to a specific value passed into this |
| * function. |
| * |
| * @param image the source image |
| * @param rectangles the set of rectangles (bounding boxes) to copy from the source |
| * image |
| * @param boundingBox the bounding rectangle of the rectangle list, which can be |
| * computed by {@link ImageUtils#getBoundingRectangle} |
| * @param scale a scale factor to apply to the result, e.g. 0.5 to shrink the |
| * destination down 50%, 1.0 to leave it alone and 2.0 to zoom in by |
| * doubling the image size |
| * @param alpha the alpha (in the range 0-255) that painted bits should be set to |
| * @return a pair of the rendered cropped image, and the location within the source |
| * image that the crop begins (multiplied by the scale). May return null if |
| * there are no selected items. |
| */ |
| public static Image drawRectangles(Image image, |
| List<Rectangle> rectangles, Rectangle boundingBox, double scale, byte alpha) { |
| |
| if (rectangles.size() == 0 || boundingBox == null || boundingBox.isEmpty()) { |
| return null; |
| } |
| |
| ImageData srcData = image.getImageData(); |
| int destWidth = (int) (scale * boundingBox.width); |
| int destHeight = (int) (scale * boundingBox.height); |
| |
| ImageData destData = new ImageData(destWidth, destHeight, srcData.depth, srcData.palette); |
| byte[] alphaData = new byte[destHeight * destWidth]; |
| destData.alphaData = alphaData; |
| |
| for (Rectangle bounds : rectangles) { |
| int dx1 = bounds.x - boundingBox.x; |
| int dy1 = bounds.y - boundingBox.y; |
| int dx2 = dx1 + bounds.width; |
| int dy2 = dy1 + bounds.height; |
| |
| dx1 *= scale; |
| dy1 *= scale; |
| dx2 *= scale; |
| dy2 *= scale; |
| |
| int sx1 = bounds.x; |
| int sy1 = bounds.y; |
| int sx2 = sx1 + bounds.width; |
| int sy2 = sy1 + bounds.height; |
| |
| if (scale == 1.0d) { |
| for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy++) { |
| for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx++) { |
| destData.setPixel(dx, dy, srcData.getPixel(sx, sy)); |
| alphaData[dy * destWidth + dx] = alpha; |
| } |
| } |
| } else { |
| // Scaled copy. |
| int sxDelta = sx2 - sx1; |
| int dxDelta = dx2 - dx1; |
| int syDelta = sy2 - sy1; |
| int dyDelta = dy2 - dy1; |
| for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy = (dy - dy1) * syDelta / dyDelta |
| + sy1) { |
| for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx = (dx - dx1) * sxDelta |
| / dxDelta + sx1) { |
| assert sx < sx2 && sy < sy2; |
| destData.setPixel(dx, dy, srcData.getPixel(sx, sy)); |
| alphaData[dy * destWidth + dx] = alpha; |
| } |
| } |
| } |
| } |
| |
| return new Image(image.getDevice(), destData); |
| } |
| |
| /** |
| * Creates a new empty/blank image of the given size |
| * |
| * @param display the display to associate the image with |
| * @param width the width of the image |
| * @param height the height of the image |
| * @return a new blank image of the given size |
| */ |
| public static Image createEmptyImage(Display display, int width, int height) { |
| BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
| return SwtUtils.convertToSwt(display, image, false, 0); |
| } |
| |
| /** |
| * Converts the given SWT {@link Rectangle} into an ADT {@link Rect} |
| * |
| * @param swtRect the SWT {@link Rectangle} |
| * @return an equivalent {@link Rect} |
| */ |
| public static Rect toRect(Rectangle swtRect) { |
| return new Rect(swtRect.x, swtRect.y, swtRect.width, swtRect.height); |
| } |
| |
| /** |
| * Sets the values of the given ADT {@link Rect} to the values of the given SWT |
| * {@link Rectangle} |
| * |
| * @param target the ADT {@link Rect} to modify |
| * @param source the SWT {@link Rectangle} to read values from |
| */ |
| public static void set(Rect target, Rectangle source) { |
| target.set(source.x, source.y, source.width, source.height); |
| } |
| |
| /** |
| * Compares an ADT {@link Rect} with an SWT {@link Rectangle} and returns true if they |
| * are equivalent |
| * |
| * @param r1 the ADT {@link Rect} |
| * @param r2 the SWT {@link Rectangle} |
| * @return true if the two rectangles are equivalent |
| */ |
| public static boolean equals(Rect r1, Rectangle r2) { |
| return r1.x == r2.x && r1.y == r2.y && r1.w == r2.width && r1.h == r2.height; |
| |
| } |
| |
| /** |
| * Get the average width of the font used by the given control |
| * |
| * @param display the display associated with the font usage |
| * @param font the font to look up the average character width for |
| * @return the average width, in pixels, of the given font |
| */ |
| public static final int getAverageCharWidth(Display display, Font font) { |
| GC gc = new GC(display); |
| gc.setFont(font); |
| FontMetrics fontMetrics = gc.getFontMetrics(); |
| int width = fontMetrics.getAverageCharWidth(); |
| gc.dispose(); |
| return width; |
| } |
| |
| /** |
| * Get the average width of the given font |
| * |
| * @param control the control to look up the default font for |
| * @return the average width, in pixels, of the current font in the control |
| */ |
| public static final int getAverageCharWidth(Control control) { |
| GC gc = new GC(control.getDisplay()); |
| int width = gc.getFontMetrics().getAverageCharWidth(); |
| gc.dispose(); |
| return width; |
| } |
| |
| /** |
| * Draws a drop shadow for the given rectangle into the given context. It |
| * will not draw anything if the rectangle is smaller than a minimum |
| * determined by the assets used to draw the shadow graphics. |
| * <p> |
| * This corresponds to {@link ImageUtils#drawRectangleShadow(Graphics, int, int, int, int)}, |
| * but applied directly to an SWT graphics context instead, such that no image conversion |
| * has to be performed. |
| * <p> |
| * Make sure to keep changes in the visual appearance here in sync with the |
| * AWT version in {@link ImageUtils#drawRectangleShadow(Graphics, int, int, int, int)}. |
| * |
| * @param gc the graphics context to draw into |
| * @param x the left coordinate of the left hand side of the rectangle |
| * @param y the top coordinate of the top of the rectangle |
| * @param width the width of the rectangle |
| * @param height the height of the rectangle |
| */ |
| public static final void drawRectangleShadow(GC gc, int x, int y, int width, int height) { |
| if (sShadowBottomLeft == null) { |
| IconFactory icons = IconFactory.getInstance(); |
| // See ImageUtils.drawRectangleShadow for an explanation of the assets. |
| sShadowBottomLeft = icons.getIcon("shadow-bl"); //$NON-NLS-1$ |
| sShadowBottom = icons.getIcon("shadow-b"); //$NON-NLS-1$ |
| sShadowBottomRight = icons.getIcon("shadow-br"); //$NON-NLS-1$ |
| sShadowRight = icons.getIcon("shadow-r"); //$NON-NLS-1$ |
| sShadowTopRight = icons.getIcon("shadow-tr"); //$NON-NLS-1$ |
| assert sShadowBottomRight.getImageData().width == SHADOW_SIZE; |
| assert sShadowBottomRight.getImageData().height == SHADOW_SIZE; |
| } |
| |
| ImageData bottomLeftData = sShadowBottomLeft.getImageData(); |
| ImageData topRightData = sShadowTopRight.getImageData(); |
| ImageData bottomData = sShadowBottom.getImageData(); |
| ImageData rightData = sShadowRight.getImageData(); |
| int blWidth = bottomLeftData.width; |
| int trHeight = topRightData.height; |
| if (width < blWidth) { |
| return; |
| } |
| if (height < trHeight) { |
| return; |
| } |
| |
| gc.drawImage(sShadowBottomLeft, x, y + height); |
| gc.drawImage(sShadowBottomRight, x + width, y + height); |
| gc.drawImage(sShadowTopRight, x + width, y); |
| gc.drawImage(sShadowBottom, |
| 0, 0, |
| bottomData.width, bottomData.height, |
| x + bottomLeftData.width, y + height, |
| width - bottomLeftData.width, bottomData.height); |
| gc.drawImage(sShadowRight, |
| 0, 0, |
| rightData.width, rightData.height, |
| x + width, y + topRightData.height, |
| rightData.width, height - topRightData.height); |
| } |
| |
| private static Image sShadowBottomLeft; |
| private static Image sShadowBottom; |
| private static Image sShadowBottomRight; |
| private static Image sShadowRight; |
| private static Image sShadowTopRight; |
| } |