1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.ide.eclipse.adt.internal.editors.layout.gle2; 17 18 import com.android.ide.common.api.Rect; 19 20 import org.eclipse.swt.SWTException; 21 import org.eclipse.swt.graphics.Image; 22 import org.eclipse.swt.graphics.ImageData; 23 import org.eclipse.swt.graphics.PaletteData; 24 import org.eclipse.swt.graphics.Rectangle; 25 import org.eclipse.swt.widgets.Display; 26 27 import java.awt.Graphics; 28 import java.awt.image.BufferedImage; 29 import java.awt.image.DataBuffer; 30 import java.awt.image.DataBufferByte; 31 import java.awt.image.DataBufferInt; 32 import java.awt.image.WritableRaster; 33 import java.util.List; 34 35 /** 36 * Various generic SWT utilities such as image conversion. 37 */ 38 public class SwtUtils { 39 40 private SwtUtils() { 41 } 42 43 /** 44 * Returns the {@link PaletteData} describing the ARGB ordering expected from integers 45 * representing pixels for AWT {@link BufferedImage}. 46 * 47 * @param imageType the {@link BufferedImage#getType()} type 48 * @return A new {@link PaletteData} suitable for AWT images. 49 */ 50 public static PaletteData getAwtPaletteData(int imageType) { 51 switch (imageType) { 52 case BufferedImage.TYPE_INT_RGB: 53 case BufferedImage.TYPE_INT_ARGB: 54 case BufferedImage.TYPE_INT_ARGB_PRE: 55 return new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF); 56 57 case BufferedImage.TYPE_3BYTE_BGR: 58 case BufferedImage.TYPE_4BYTE_ABGR: 59 case BufferedImage.TYPE_4BYTE_ABGR_PRE: 60 return new PaletteData(0x000000FF, 0x0000FF00, 0x00FF0000); 61 62 default: 63 throw new UnsupportedOperationException("RGB type not supported yet."); 64 } 65 } 66 67 /** 68 * Returns true if the given type of {@link BufferedImage} is supported for 69 * conversion. For unsupported formats, use 70 * {@link #convertToCompatibleFormat(BufferedImage)} first. 71 * 72 * @param imageType the {@link BufferedImage#getType()} 73 * @return true if we can convert the given buffered image format 74 */ 75 private static boolean isSupportedPaletteType(int imageType) { 76 switch (imageType) { 77 case BufferedImage.TYPE_INT_RGB: 78 case BufferedImage.TYPE_INT_ARGB: 79 case BufferedImage.TYPE_INT_ARGB_PRE: 80 case BufferedImage.TYPE_3BYTE_BGR: 81 case BufferedImage.TYPE_4BYTE_ABGR: 82 case BufferedImage.TYPE_4BYTE_ABGR_PRE: 83 return true; 84 default: 85 return false; 86 } 87 } 88 89 /** Converts the given arbitrary {@link BufferedImage} to another {@link BufferedImage} 90 * in a format that is supported (see {@link #isSupportedPaletteType(int)}) 91 * 92 * @param image the image to be converted 93 * @return a new image that is in a guaranteed compatible format 94 */ 95 private static BufferedImage convertToCompatibleFormat(BufferedImage image) { 96 BufferedImage converted = new BufferedImage(image.getWidth(), image.getHeight(), 97 BufferedImage.TYPE_INT_ARGB); 98 Graphics graphics = converted.getGraphics(); 99 graphics.drawImage(image, 0, 0, null); 100 graphics.dispose(); 101 102 return converted; 103 } 104 105 /** 106 * Converts an AWT {@link BufferedImage} into an equivalent SWT {@link Image}. Whether 107 * the transparency data is transferred is optional, and this method can also apply an 108 * alpha adjustment during the conversion. 109 * <p/> 110 * Implementation details: on Windows, the returned {@link Image} will have an ordering 111 * matching the Windows DIB (e.g. RGBA, not ARGB). Callers must make sure to use 112 * <code>Image.getImageData().paletteData</code> to get the right pixels out of the image. 113 * 114 * @param display The display where the SWT image will be shown 115 * @param awtImage The AWT {@link BufferedImage} 116 * @param transferAlpha If true, copy alpha data out of the source image 117 * @param globalAlpha If -1, do nothing, otherwise adjust the alpha of the final image 118 * by the given amount in the range [0,255] 119 * @return A new SWT {@link Image} with the same contents as the source 120 * {@link BufferedImage} 121 */ 122 public static Image convertToSwt(Display display, BufferedImage awtImage, 123 boolean transferAlpha, int globalAlpha) { 124 if (!isSupportedPaletteType(awtImage.getType())) { 125 awtImage = convertToCompatibleFormat(awtImage); 126 } 127 128 int width = awtImage.getWidth(); 129 int height = awtImage.getHeight(); 130 131 WritableRaster raster = awtImage.getRaster(); 132 DataBuffer dataBuffer = raster.getDataBuffer(); 133 ImageData imageData = 134 new ImageData(width, height, 32, getAwtPaletteData(awtImage.getType())); 135 136 if (dataBuffer instanceof DataBufferInt) { 137 int[] imageDataBuffer = ((DataBufferInt) dataBuffer).getData(); 138 imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); 139 } else if (dataBuffer instanceof DataBufferByte) { 140 byte[] imageDataBuffer = ((DataBufferByte) dataBuffer).getData(); 141 try { 142 imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); 143 } catch (SWTException se) { 144 // Unsupported depth 145 return convertToSwt(display, convertToCompatibleFormat(awtImage), 146 transferAlpha, globalAlpha); 147 } 148 } 149 150 if (transferAlpha) { 151 byte[] alphaData = new byte[height * width]; 152 for (int y = 0; y < height; y++) { 153 byte[] alphaRow = new byte[width]; 154 for (int x = 0; x < width; x++) { 155 int alpha = awtImage.getRGB(x, y) >>> 24; 156 157 // We have to multiply in the alpha now since if we 158 // set ImageData.alpha, it will ignore the alphaData. 159 if (globalAlpha != -1) { 160 alpha = alpha * globalAlpha >> 8; 161 } 162 163 alphaRow[x] = (byte) alpha; 164 } 165 System.arraycopy(alphaRow, 0, alphaData, y * width, width); 166 } 167 168 imageData.alphaData = alphaData; 169 } else if (globalAlpha != -1) { 170 imageData.alpha = globalAlpha; 171 } 172 173 return new Image(display, imageData); 174 } 175 176 /** 177 * Converts a direct-color model SWT image to an equivalent AWT image. If the image 178 * does not have a supported color model, returns null. This method does <b>NOT</b> 179 * preserve alpha in the source image. 180 * 181 * @param swtImage the SWT image to be converted to AWT 182 * @return an AWT image representing the source SWT image 183 */ 184 public static BufferedImage convertToAwt(Image swtImage) { 185 ImageData swtData = swtImage.getImageData(); 186 BufferedImage awtImage = 187 new BufferedImage(swtData.width, swtData.height, BufferedImage.TYPE_INT_ARGB); 188 PaletteData swtPalette = swtData.palette; 189 if (swtPalette.isDirect) { 190 PaletteData awtPalette = getAwtPaletteData(awtImage.getType()); 191 192 if (swtPalette.equals(awtPalette)) { 193 // No color conversion needed. 194 for (int y = 0; y < swtData.height; y++) { 195 for (int x = 0; x < swtData.width; x++) { 196 int pixel = swtData.getPixel(x, y); 197 awtImage.setRGB(x, y, 0xFF000000 | pixel); 198 } 199 } 200 } else { 201 // We need to remap the colors 202 int sr = -awtPalette.redShift + swtPalette.redShift; 203 int sg = -awtPalette.greenShift + swtPalette.greenShift; 204 int sb = -awtPalette.blueShift + swtPalette.blueShift; 205 206 for (int y = 0; y < swtData.height; y++) { 207 for (int x = 0; x < swtData.width; x++) { 208 int pixel = swtData.getPixel(x, y); 209 210 int r = pixel & swtPalette.redMask; 211 int g = pixel & swtPalette.greenMask; 212 int b = pixel & swtPalette.blueMask; 213 r = (sr < 0) ? r >>> -sr : r << sr; 214 g = (sg < 0) ? g >>> -sg : g << sg; 215 b = (sb < 0) ? b >>> -sb : b << sb; 216 217 pixel = 0xFF000000 | r | g | b; 218 awtImage.setRGB(x, y, pixel); 219 } 220 } 221 } 222 } else { 223 return null; 224 } 225 226 return awtImage; 227 } 228 229 /** 230 * Creates a new image from a source image where the contents from a given set of 231 * bounding boxes are copied into the new image and the rest is left transparent. A 232 * scale can be applied to make the resulting image larger or smaller than the source 233 * image. Note that the alpha channel in the original image is ignored, and the alpha 234 * values for the painted rectangles will be set to a specific value passed into this 235 * function. 236 * 237 * @param image the source image 238 * @param rectangles the set of rectangles (bounding boxes) to copy from the source 239 * image 240 * @param boundingBox the bounding rectangle of the rectangle list, which can be 241 * computed by {@link ImageUtils#getBoundingRectangle} 242 * @param scale a scale factor to apply to the result, e.g. 0.5 to shrink the 243 * destination down 50%, 1.0 to leave it alone and 2.0 to zoom in by 244 * doubling the image size 245 * @param alpha the alpha (in the range 0-255) that painted bits should be set to 246 * @return a pair of the rendered cropped image, and the location within the source 247 * image that the crop begins (multiplied by the scale). May return null if 248 * there are no selected items. 249 */ 250 public static Image drawRectangles(Image image, 251 List<Rectangle> rectangles, Rectangle boundingBox, double scale, byte alpha) { 252 253 if (rectangles.size() == 0 || boundingBox == null || boundingBox.isEmpty()) { 254 return null; 255 } 256 257 ImageData srcData = image.getImageData(); 258 int destWidth = (int) (scale * boundingBox.width); 259 int destHeight = (int) (scale * boundingBox.height); 260 261 ImageData destData = new ImageData(destWidth, destHeight, srcData.depth, srcData.palette); 262 byte[] alphaData = new byte[destHeight * destWidth]; 263 destData.alphaData = alphaData; 264 265 for (Rectangle bounds : rectangles) { 266 int dx1 = bounds.x - boundingBox.x; 267 int dy1 = bounds.y - boundingBox.y; 268 int dx2 = dx1 + bounds.width; 269 int dy2 = dy1 + bounds.height; 270 271 dx1 *= scale; 272 dy1 *= scale; 273 dx2 *= scale; 274 dy2 *= scale; 275 276 int sx1 = bounds.x; 277 int sy1 = bounds.y; 278 int sx2 = sx1 + bounds.width; 279 int sy2 = sy1 + bounds.height; 280 281 if (scale == 1.0d) { 282 for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy++) { 283 for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx++) { 284 destData.setPixel(dx, dy, srcData.getPixel(sx, sy)); 285 alphaData[dy * destWidth + dx] = alpha; 286 } 287 } 288 } else { 289 // Scaled copy. 290 int sxDelta = sx2 - sx1; 291 int dxDelta = dx2 - dx1; 292 int syDelta = sy2 - sy1; 293 int dyDelta = dy2 - dy1; 294 for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy = (dy - dy1) * syDelta / dyDelta 295 + sy1) { 296 for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx = (dx - dx1) * sxDelta 297 / dxDelta + sx1) { 298 assert sx < sx2 && sy < sy2; 299 destData.setPixel(dx, dy, srcData.getPixel(sx, sy)); 300 alphaData[dy * destWidth + dx] = alpha; 301 } 302 } 303 } 304 } 305 306 return new Image(image.getDevice(), destData); 307 } 308 309 /** 310 * Creates a new empty/blank image of the given size 311 * 312 * @param display the display to associate the image with 313 * @param width the width of the image 314 * @param height the height of the image 315 * @return a new blank image of the given size 316 */ 317 public static Image createEmptyImage(Display display, int width, int height) { 318 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 319 return SwtUtils.convertToSwt(display, image, false, 0); 320 } 321 322 /** 323 * Converts the given SWT {@link Rectangle} into an ADT {@link Rect} 324 * 325 * @param swtRect the SWT {@link Rectangle} 326 * @return an equivalent {@link Rect} 327 */ 328 public static Rect toRect(Rectangle swtRect) { 329 return new Rect(swtRect.x, swtRect.y, swtRect.width, swtRect.height); 330 } 331 332 /** 333 * Sets the values of the given ADT {@link Rect} to the values of the given SWT 334 * {@link Rectangle} 335 * 336 * @param target the ADT {@link Rect} to modify 337 * @param source the SWT {@link Rectangle} to read values from 338 */ 339 public static void set(Rect target, Rectangle source) { 340 target.set(source.x, source.y, source.width, source.height); 341 } 342 343 /** 344 * Compares an ADT {@link Rect} with an SWT {@link Rectangle} and returns true if they 345 * are equivalent 346 * 347 * @param r1 the ADT {@link Rect} 348 * @param r2 the SWT {@link Rectangle} 349 * @return true if the two rectangles are equivalent 350 */ 351 public static boolean equals(Rect r1, Rectangle r2) { 352 return r1.x == r2.x && r1.y == r2.y && r1.w == r2.width && r1.h == r2.height; 353 354 } 355 } 356