aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/Pair.java28
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java49
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java28
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java28
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java9
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java141
10 files changed, 279 insertions, 43 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/Pair.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/Pair.java
index 950f6b9..e959321 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/Pair.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/Pair.java
@@ -27,7 +27,7 @@ package com.android.ide.common.layout;
* @param <S> The type of the first value
* @param <T> The type of the second value
*/
-class Pair<S,T> {
+public class Pair<S,T> {
private final S mFirst;
private final T mSecond;
@@ -37,19 +37,35 @@ class Pair<S,T> {
this.mSecond = second;
}
- /** Return the first item in the pair */
+ /**
+ * Return the first item in the pair
+ *
+ * @return the first item in the pair
+ */
public S getFirst() {
return mFirst;
}
- /** Return the second item in the pair */
+ /**
+ * Return the second item in the pair
+ *
+ * @return the second item in the pair
+ */
public T getSecond() {
return mSecond;
}
- /** Constructs a new pair of the given two objects, inferring generic types. */
- public static <S,T> Pair<S,T> of(S a, T b) {
- return new Pair<S,T>(a,b);
+ /**
+ * Constructs a new pair of the given two objects, inferring generic types.
+ *
+ * @param first the first item to store in the pair
+ * @param second the second item to store in the pair
+ * @param <S> the type of the first item
+ * @param <T> the type of the second item
+ * @return a new pair wrapping the two items
+ */
+ public static <S,T> Pair<S,T> of(S first, T second) {
+ return new Pair<S,T>(first,second);
}
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java
index 3648258..de9abe9 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java
@@ -49,7 +49,8 @@ import java.util.List;
/**
* Creates a new {@link CanvasSelection} object.
* @param canvasViewInfo The view info being selected. Must not be null.
- * @param nodeFactory
+ * @param gre the rules engine
+ * @param nodeFactory the node factory
*/
public CanvasSelection(CanvasViewInfo canvasViewInfo,
RulesEngine gre,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
index cb947d2..2c7bd73 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
@@ -33,6 +33,9 @@ import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Rectangle;
import java.util.ArrayList;
import java.util.List;
@@ -485,6 +488,8 @@ public class GestureManager {
* {@inheritDoc}
*/
public void dragStart(DragSourceEvent e) {
+ LayoutPoint p = LayoutPoint.create(mCanvas, e);
+
// We need a selection (simple or multiple) to do any transfer.
// If there's a selection *and* the cursor is over this selection,
// use all the currently selected elements.
@@ -497,8 +502,6 @@ public class GestureManager {
if (!selections.isEmpty()) {
// Is the cursor on top of a selected element?
- LayoutPoint p = LayoutPoint.create(mCanvas, e);
-
boolean insideSelection = false;
for (CanvasSelection cs : selections) {
@@ -540,7 +543,6 @@ public class GestureManager {
// If you are dragging a non-selected item, select it
if (mDragSelection.isEmpty()) {
- LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout();
CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
if (vi != null && !vi.isRoot()) {
mCanvas.getSelectionManager().selectSingle(vi);
@@ -551,10 +553,11 @@ public class GestureManager {
SelectionManager.sanitize(mDragSelection);
e.doit = !mDragSelection.isEmpty();
+ int imageCount = mDragSelection.size();
if (e.doit) {
mDragElements = CanvasSelection.getAsElements(mDragSelection);
GlobalCanvasDragInfo.getInstance().startDrag(mDragElements,
- mDragSelection.toArray(new CanvasSelection[mDragSelection.size()]),
+ mDragSelection.toArray(new CanvasSelection[imageCount]),
mCanvas, new Runnable() {
public void run() {
mCanvas.getClipboardSupport().deleteSelection("Remove",
@@ -565,7 +568,7 @@ public class GestureManager {
// If you drag on the -background-, we make that into a marquee
// selection
- if (!e.doit || (mDragSelection.size() == 1 && mDragSelection.get(0).isRoot())) {
+ if (!e.doit || (imageCount == 1 && mDragSelection.get(0).isRoot())) {
boolean toggle = (mLastStateMask & (SWT.CTRL | SWT.SHIFT | SWT.COMMAND)) != 0;
startGesture(ControlPoint.create(mCanvas, e),
new MarqueeGesture(mCanvas, toggle), mLastStateMask);
@@ -575,6 +578,42 @@ public class GestureManager {
// Otherwise, the drag means you are moving something
mCanvas.showInvisibleViews(true);
startGesture(ControlPoint.create(mCanvas, e), new MoveGesture(mCanvas), 0);
+
+ // Render drag-images: Copy portions of the full screen render.
+ Image image = mCanvas.getImageOverlay().getImage();
+ if (image != null) {
+ /**
+ * Transparency of the dragged image ([0-255]). We're using 30%
+ * translucency to make the image faint and not obscure the drag
+ * feedback below it.
+ */
+ final byte DRAG_TRANSPARENCY = (byte) (0.3 * 255);
+
+ List<Rectangle> rectangles = new ArrayList<Rectangle>(imageCount);
+ if (imageCount > 0) {
+ ImageData data = image.getImageData();
+ Rectangle imageRectangle = new Rectangle(0, 0, data.width, data.height);
+ for (CanvasSelection item : mDragSelection) {
+ Rectangle bounds = item.getRect();
+ // Some bounds can be outside the rendered rectangle (for
+ // example, in an absolute layout, you can have negative
+ // coordinates), so create the intersection of these bounds.
+ Rectangle clippedBounds = imageRectangle.intersection(bounds);
+ rectangles.add(clippedBounds);
+ }
+ Rectangle boundingBox = ImageUtils.getBoundingRectangle(rectangles);
+ double scale = mCanvas.getHorizontalTransform().getScale();
+ e.image = SwtUtils.drawRectangles(image, rectangles, boundingBox, scale,
+ DRAG_TRANSPARENCY);
+
+ // Set the image offset such that we preserve the relative
+ // distance between the mouse pointer and the top left corner of
+ // the dragged view
+ int deltaX = (int) (scale * (boundingBox.x - p.x));
+ int deltaY = (int) (scale * (boundingBox.y - p.y));
+ SwtUtils.setDragImageOffsets(e, -deltaX, -deltaY);
+ }
+ }
}
// No hover during drag (since no mouse over events are delivered
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 22a1a88..0243764 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -391,9 +391,20 @@ public class GraphicalEditorPart extends EditorPart
double s = mCanvasViewer.getCanvas().getScale();
if (direction > 0) {
- s = s * 2;
+ s = s * 1.2;
} else {
- s = s / 2;
+ s = s / 1.2;
+ }
+
+ // Some operations are faster if the zoom is EXACTLY 1.0 rather than ALMOST 1.0.
+ // (This is because there is a fast-path when image copying and the scale is 1.0;
+ // in that case it does not have to do any scaling).
+ //
+ // If you zoom out 10 times and then back in 10 times, small rounding errors mean
+ // that you end up with a scale=1.0000000000000004. In the cases, when you get close
+ // to 1.0, just make the zoom an exact 1.0.
+ if (Math.abs(s-1.0) < 0.0001) {
+ s = 1.0;
}
mCanvasViewer.getCanvas().setScale(s, true /*redraw*/);
@@ -1666,16 +1677,9 @@ public class GraphicalEditorPart extends EditorPart
boolean isFrameworkResource = false;
if (colon != -1) {
// The URL contains a package name.
-
- // FIXME: We should consult the package and Do The Right Thing.
- // If the package is @android (which is by far the most common case),
- // then maybe we can look up the corresponding file in the "data/" folder
- // in the SDK.
- // Otherwise, the package MAY be the same package as the current project,
- // in which case we can just ignore it (because it will be exactly
- // relative to the current project's folder), and otherwise we may
- // have to look in other projects. Fortunately, this is not common.
-
+ // While the url format technically allows other package names,
+ // the platform apparently only supports @android for now (or if it does,
+ // there are no usages in the current code base so this is not common).
String packageName = url.substring(typeBegin, colon);
if ("android".equals(packageName)) { //$NON-NLS-1$
isFrameworkResource = true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
index 337e76e..b920607 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
@@ -90,6 +90,15 @@ public class ImageOverlay extends Overlay {
return mImage;
}
+ /**
+ * Returns the currently painted image, or null if none has been set
+ *
+ * @return the currently painted image or null
+ */
+ public Image getImage() {
+ return mImage;
+ }
+
@Override
public void paint(GC gc) {
if (mImage != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
index 21ad07c..f83d4a5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
@@ -17,10 +17,14 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.Rect;
+import org.eclipse.swt.graphics.Rectangle;
+
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
+import java.util.Iterator;
+import java.util.List;
/**
* Utilities related to image processing.
@@ -323,4 +327,28 @@ public class ImageUtils {
return image;
}
+
+ /**
+ * Returns a bounding rectangle for the given list of rectangles. If the list is
+ * empty, the bounding rectangle is null.
+ *
+ * @param items the list of rectangles to compute a bounding rectangle for (may not be
+ * null)
+ * @return a bounding rectangle of the passed in rectangles, or null if the list is
+ * empty
+ */
+ public static Rectangle getBoundingRectangle(List<Rectangle> items) {
+ Iterator<Rectangle> iterator = items.iterator();
+ if (!iterator.hasNext()) {
+ return null;
+ }
+
+ Rectangle bounds = iterator.next();
+ Rectangle union = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
+ while (iterator.hasNext()) {
+ union.add(iterator.next());
+ }
+
+ return union;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index e5113f8..8f5f048 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -396,6 +396,15 @@ class LayoutCanvas extends Canvas {
}
/**
+ * Returns the current {@link ImageOverlay} painting the rendered result
+ *
+ * @return the image overlay responsible for painting the rendered result, never null
+ */
+ /* package */ ImageOverlay getImageOverlay() {
+ return mImageOverlay;
+ }
+
+ /**
* Returns the horizontal {@link ScaleInfo} transform object, which can map
* a layout point into a control point.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
index 2b01b1c..142d8eb 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java
@@ -72,7 +72,6 @@ import org.w3c.dom.Element;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.StringWriter;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@@ -630,22 +629,10 @@ public class PaletteComposite extends Composite {
// Shift the drag feedback image up such that it's centered under the
// mouse pointer
- // Eclipse 3.4 does not support drag image offsets
- // TODO: Replace by direct field access when we drop Eclipse 3.4 support.
- try {
- Field xField = event.getClass().getDeclaredField("offsetX"); //$NON-NLS-1$
- Field yField = event.getClass().getDeclaredField("offsetY"); //$NON-NLS-1$
-
- Rectangle imageBounds = mImage.getBounds();
- int offsetX = imageBounds.width / 2;
- int offsetY = imageBounds.height / 2;
- xField.set(event, Integer.valueOf(offsetX));
- yField.set(event, Integer.valueOf(offsetY));
- } catch (SecurityException e) {
- } catch (NoSuchFieldException e) {
- } catch (IllegalArgumentException e) {
- } catch (IllegalAccessException e) {
- }
+ Rectangle imageBounds = mImage.getBounds();
+ int offsetX = imageBounds.width / 2;
+ int offsetY = imageBounds.height / 2;
+ SwtUtils.setDragImageOffsets(event, offsetX, offsetY);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
index 3b85a3a..136414e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
@@ -361,6 +361,12 @@ public class SelectionManager implements ISelectionProvider {
// reset alternate selection if any
mAltSelection = null;
+ if (vi == null) {
+ // The user clicked outside the bounds of the root element; in that case, just
+ // select the root element.
+ vi = mCanvas.getViewHierarchy().getRoot();
+ }
+
boolean redoLayout = hasExplodedItems();
// reset (multi)selection if any
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java
index 88f91a0..66c3817 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java
@@ -16,7 +16,9 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.Rect;
+import com.sun.tools.javac.util.Pair;
+import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
@@ -26,6 +28,8 @@ import org.eclipse.swt.widgets.Display;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
+import java.lang.reflect.Field;
+import java.util.List;
/**
* Various generic SWT utilities such as image conversion.
@@ -36,9 +40,10 @@ public class SwtUtils {
}
/**
- * Returns the {@link PaletteData} describing the ARGB ordering expected from
- * integers representing pixels for AWT {@link BufferedImage}.
+ * 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) {
@@ -168,6 +173,138 @@ public class SwtUtils {
}
/**
+ * Sets the DragSourceEvent's offsetX and offsetY fields.
+ *
+ * @param event the {@link DragSourceEvent}
+ * @param offsetX the offset X value
+ * @param offsetY the offset Y value
+ */
+ public static void setDragImageOffsets(DragSourceEvent event, int offsetX, int offsetY) {
+ // Eclipse 3.4 does not support drag image offsets
+ // event.offsetX = offsetX;
+ // event.offsetY= offsetY;
+ // FIXME: Replace by direct field access when we drop Eclipse 3.4 support.
+ try {
+ Class<DragSourceEvent> clz = DragSourceEvent.class;
+ Field xField = clz.getDeclaredField("offsetX"); //$NON-NLS-1$
+ Field yField = clz.getDeclaredField("offsetY"); //$NON-NLS-1$
+ xField.set(event, Integer.valueOf(offsetX));
+ yField.set(event, Integer.valueOf(offsetY));
+ } catch (SecurityException e) {
+ } catch (NoSuchFieldException e) {
+ } catch (IllegalArgumentException e) {
+ } catch (IllegalAccessException e) {
+ }
+ }
+
+ /**
+ * Returns the DragSourceEvent's offsetX and offsetY fields.
+ *
+ * @param event the {@link DragSourceEvent}
+ * @return A pair of the offset X and Y values, or null if it fails (e.g. on Eclipse
+ * 3.4)
+ */
+ public static Pair<Integer,Integer> getDragImageOffsets(DragSourceEvent event) {
+ // Eclipse 3.4 does not support drag image offsets:
+ // return Pair.of(event.offsetX, event.offsetY);
+ // FIXME: Replace by direct field access when we drop Eclipse 3.4 support.
+ try {
+ Class<DragSourceEvent> clz = DragSourceEvent.class;
+ Field xField = clz.getDeclaredField("offsetX"); //$NON-NLS-1$
+ Field yField = clz.getDeclaredField("offsetY"); //$NON-NLS-1$
+ int offsetX = xField.getInt(event);
+ int offsetY = yField.getInt(event);
+ return Pair.of(offsetX, offsetY);
+ } catch (SecurityException e) {
+ } catch (NoSuchFieldException e) {
+ } catch (IllegalArgumentException e) {
+ } catch (IllegalAccessException e) {
+ }
+
+ return null;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
* Converts the given SWT {@link Rectangle} into an ADT {@link Rect}
*
* @param swtRect the SWT {@link Rectangle}