Bug 496409: [HiDPI][API] Provide Image#getImageData(int zoom)
- Cocoa and GTK implementation for getImageData(int zoom) method.

Change-Id: I8db02a3c4dad01813f1d9b46bd6518c9624d9ede
Also-by: Niraj Modi <niraj.modi@in.ibm.com>
Signed-off-by: Markus Keller <markus_keller@ch.ibm.com>
Signed-off-by: Lakshmi Shanmugam <lshanmug@in.ibm.com>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java
index 615da81..b52ae6f 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java
@@ -1248,20 +1248,7 @@
  * @see ImageData
  */
 public ImageData getImageData() {
-	if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
-	NSAutoreleasePool pool = null;
-	if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
-	try {
-		NSBitmapImageRep imageRep;
-		if (imageFileNameProvider == null && imageDataProvider == null) {
-			imageRep = getRepresentation();
-		} else {
-			imageRep = getRepresentation_100();
-		}
-		return _getImageData(imageRep, this.alphaInfo_100);
-	} finally {
-		if (pool != null) pool.release();
-	}
+	return getImageData(100);
 }
 
 /**
@@ -1271,8 +1258,6 @@
  * <p>
  * <b>Warning:</b> This API doesn't make sense and will be replaced, see
  * <a href="https://bugs.eclipse.org/496409">bug 496409</a>.
- * Until then, it will return an ImageData for the highest supported resolution
- * (e.g. always the 200% version on macOS).
  * </p>
  *
  * @return an <code>ImageData</code> containing the image's data
@@ -1285,44 +1270,70 @@
  *
  * @see ImageData
  * @since 3.105
+ * @deprecated use {@link #getImageData(int)} instead
  */
+@Deprecated
 public ImageData getImageDataAtCurrentZoom() {
-	/*
-	 * Bug 496409 in SWT: getImageDataAtCurrentZoom() doesn't make sense.
-	 *
-	 * The current implementation on cocoa returns the 200% representation if there's an image provider,
-	 * even if DPIUtil.getDeviceZoom() == 100. That sounds wrong, but it's crucial for clients
-	 * that this behavior keeps working.
-	 *
-	 * Reason: The Image constructor wrongly creates 200% images even if
-	 * DPIUtil.getDeviceZoom() == 100 (bug 462555). A consequence of that bug is that
-	 * clients get callbacks to ImageDataProvider#getImageData(int) for zoom == 200.
-	 * To compute e.g. a composite image, clients need access to the 200% image data.
-	 * Currently, the only way to do that is via this method.
-	 */
+	return getImageData(DPIUtil.getDeviceZoom());
+}
+
+/**
+ * Returns an {@link ImageData} for the given zoom level based on the
+ * receiver.
+ * <p>
+ * Note that this method is mainly intended to be used by custom
+ * implementations of {@link ImageDataProvider} that draw a composite image
+ * at the requested zoom level based on other images. For custom zoom
+ * levels, the image data may be an auto-scaled version of the native image
+ * and may look more blurred or mangled than expected.
+ * </p>
+ * <p>
+ * Modifications made to the returned {@code ImageData} will not affect this
+ * {@code Image}.
+ * </p>
+ *
+ * @param zoom
+ *            The zoom level in % of the standard resolution (which is 1
+ *            physical monitor pixel == 1 SWT logical pixel). Typically 100,
+ *            150, or 200.
+ * @return an <code>ImageData</code> containing the image's data and
+ *         attributes at the given zoom level
+ *
+ * @exception SWTException <ul>
+ *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
+ *    <li>ERROR_INVALID_IMAGE - if the image is not a bitmap or an icon</li>
+ * </ul>
+ *
+ * @since 3.106
+ */
+public ImageData getImageData(int zoom) {
 	if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
 	NSAutoreleasePool pool = null;
 	if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init();
 	try {
-		NSBitmapImageRep imageRep = getRepresentation();
-		ImageData data;
 		boolean hasImageProvider = imageFileNameProvider != null || imageDataProvider != null;
-		if (hasImageProvider) {
-			NSBitmapImageRep imageRep200 = getRepresentation_200();
-			if (imageRep200 != null) {
-				if (alphaInfo_100.alphaData != null && alphaInfo_200 != null) {
-					if (alphaInfo_200.alphaData == null) initAlpha_200(imageRep);
+		if (zoom == 100) {
+			NSBitmapImageRep imageRep;
+			imageRep = hasImageProvider ? getRepresentation_100() : getRepresentation();
+			return _getImageData(imageRep, alphaInfo_100);
+		}
+		if (zoom == 200) {
+			if (hasImageProvider) {
+				NSBitmapImageRep imageRep200 = getRepresentation_200();
+				if (imageRep200 != null) {
+					if (alphaInfo_100.alphaData != null && alphaInfo_200 != null) {
+						if (alphaInfo_200.alphaData == null) initAlpha_200(imageRep200);
+					}
+					return _getImageData(imageRep200, alphaInfo_200);
 				}
-				return _getImageData(imageRep, alphaInfo_200);
 			}
 		}
-		// XXX: No HiDPI representation available: Need to scale 100% rep. Workaround for bug 513129 and bug 513637.
-		data = _getImageData(imageRep, this.alphaInfo_100);
-		return DPIUtil.autoScaleImageData(device, data, DPIUtil.getDeviceZoom(), 100);
 	} finally {
 		if (pool != null) pool.release();
 	}
+	return DPIUtil.autoScaleImageData (device, getImageData(), zoom, 100);
 }
+
 /** Returns the best available representation. May be 100% or 200% iff there is an image provider. */
 NSBitmapImageRep getRepresentation () {
 	NSBitmapImageRep rep = new NSBitmapImageRep(handle.bestRepresentationForDevice(null));
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java
index ac9a7b0..520b8d9 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java
@@ -208,7 +208,7 @@
  * Auto-scale image with ImageData
  */
 public static ImageData autoScaleImageData (Device device, final ImageData imageData, int targetZoom, int currentZoom) {
-	if (deviceZoom == 100 || imageData == null || targetZoom == currentZoom || (device != null && !device.isAutoScalable())) return imageData;
+	if (imageData == null || targetZoom == currentZoom || (device != null && !device.isAutoScalable())) return imageData;
 	float scaleFactor = (float) targetZoom / (float) currentZoom;
 	return autoScaleImageData(device, imageData, scaleFactor);
 }
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java
index 77abfdc..da93696 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2017 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
@@ -1335,8 +1335,6 @@
  * <p>
  * <b>Warning:</b> This API doesn't make sense and will be replaced, see
  * <a href="https://bugs.eclipse.org/496409">bug 496409</a>.
- * Until then, it will return an ImageData for the highest supported resolution
- * (e.g. always the 200% version on macOS).
  * </p>
  *
  * @return an <code>ImageData</code> containing the image's data
@@ -1349,7 +1347,9 @@
  *
  * @see ImageData
  * @since 3.105
+ * @deprecated use {@link #getImageData(int)} instead
  */
+@Deprecated
 public ImageData getImageDataAtCurrentZoom () {
 	if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
 
@@ -1459,29 +1459,60 @@
 }
 
 /**
- * Returns an <code>ImageData</code> for specified zoom, based on the receiver
- * Modifications made to this <code>ImageData</code> will not affect the
- * Image.
+ * Returns an {@link ImageData} for the given zoom level based on the
+ * receiver.
+ * <p>
+ * Note that this method is mainly intended to be used by custom
+ * implementations of {@link ImageDataProvider} that draw a composite image
+ * at the requested zoom level based on other images. For custom zoom
+ * levels, the image data may be an auto-scaled version of the native image
+ * and may look more blurred or mangled than expected.
+ * </p>
+ * <p>
+ * Modifications made to the returned {@code ImageData} will not affect this
+ * {@code Image}.
+ * </p>
  *
  * @param zoom
  *            The zoom level in % of the standard resolution (which is 1
  *            physical monitor pixel == 1 SWT logical pixel). Typically 100,
  *            150, or 200.
  * @return an <code>ImageData</code> containing the image's data and
- *         attributes at specified zoom if present else null is returned.
+ *         attributes at the given zoom level
  *
  * @exception SWTException <ul>
  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
  *    <li>ERROR_INVALID_IMAGE - if the image is not a bitmap or an icon</li>
  * </ul>
  *
- * @see ImageData
- *
- * @since 3.105
+ * @since 3.106
  */
-ImageData getImageData (int zoom) {
+public ImageData getImageData (int zoom) {
 	if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
-	return DPIUtil.autoScaleImageData (device, getImageDataAtCurrentZoom (), zoom, currentDeviceZoom);
+
+	if (zoom == currentDeviceZoom) {
+		return getImageDataAtCurrentZoom();
+	} else if (imageDataProvider != null) {
+		boolean[] found = new boolean[1];
+		ImageData data = DPIUtil.validateAndGetImageDataAtZoom (imageDataProvider, zoom, found);
+		// exact image found
+		if (found[0]) {
+			return data;
+		}
+		// AutoScale the image at 100% zoom
+		return DPIUtil.autoScaleUp (device, data);
+	} else if (imageFileNameProvider != null) {
+		boolean[] found = new boolean[1];
+		String fileName = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, zoom, found);
+		// exact image found
+		if (found[0]) {
+			return new ImageData (fileName);
+		}
+		// AutoScale the image at 100% zoom
+		return DPIUtil.autoScaleUp (device, new ImageData (fileName));
+	} else {
+		return DPIUtil.autoScaleImageData (device, getImageDataAtCurrentZoom (), zoom, currentDeviceZoom);
+	}
 }
 
 /**
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/ImageList.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/ImageList.java
index c0015ab..d1b0b55 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/ImageList.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/internal/ImageList.java
@@ -132,7 +132,7 @@
 			}
 			OS.g_object_unref(maskPixbuf);
 		} else {
-			ImageData data = image.getImageDataAtCurrentZoom ();
+			ImageData data = image.getImageData (DPIUtil.getDeviceZoom ());
 			boolean hasAlpha = data.getTransparencyType () == SWT.TRANSPARENCY_ALPHA;
 			pixbuf = OS.gdk_pixbuf_new (OS.GDK_COLORSPACE_RGB, hasAlpha, 8, w [0], h [0]);
 			if (pixbuf == 0) SWT.error (SWT.ERROR_NO_HANDLES);
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java
index c990d85..3a068fb 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java
@@ -798,6 +798,69 @@
 }
 
 @Test
+public void test_getImageData_100() {
+	getImageData_int(100);
+}
+
+@Test
+public void test_getImageData_200() {
+	getImageData_int(200);
+}
+
+void getImageData_int(int zoom) {
+	Rectangle bounds = new Rectangle(0, 0, 10, 20);
+	Image image = new Image(display, bounds.width, bounds.height);
+	image.dispose();
+	try {
+		image.getImageData(zoom);
+		fail("No exception thrown for disposed image");
+	} catch (SWTException e) {
+		assertSWTProblem("Incorrect exception thrown for disposed image", SWT.ERROR_GRAPHIC_DISPOSED, e);
+	}
+
+	// creates bitmap image and compare size of imageData
+	image = new Image(display, bounds.width, bounds.height);
+	ImageData imageDataAtZoom = image.getImageData(zoom);
+	image.dispose();
+	Rectangle boundsAtZoom = new Rectangle(0, 0, imageDataAtZoom.width, imageDataAtZoom.height);
+	assertEquals(":a: Size of ImageData returned from Image.getImageData(int) method doesn't return matches with bounds in Pixel values.", scaleBounds(bounds, zoom, 100), boundsAtZoom);
+
+	// create icon image and compare size of imageData
+	ImageData imageData = new ImageData(bounds.width, bounds.height, 1, new PaletteData(new RGB[] {new RGB(0, 0, 0)}));
+	image = new Image(display, imageData);
+	imageDataAtZoom = image.getImageData(zoom);
+	image.dispose();
+	boundsAtZoom = new Rectangle(0, 0, imageDataAtZoom.width, imageDataAtZoom.height);
+	assertEquals(":b: Size of ImageData returned from Image.getImageData(int) method doesn't return matches with bounds in Pixel values.", scaleBounds(bounds, zoom, 100), boundsAtZoom);
+
+	// create image with FileNameProvider
+	image = new Image(display, imageFileNameProvider);
+	imageDataAtZoom = image.getImageData(zoom);
+	boundsAtZoom = new Rectangle(0, 0, imageDataAtZoom.width, imageDataAtZoom.height);
+	bounds = image.getBounds();
+	image.dispose();
+	assertEquals(":c: Size of ImageData returned from Image.getImageData(int) method doesn't return matches with bounds in Pixel values.", scaleBounds(bounds, zoom, 100), boundsAtZoom);
+
+	// create image with ImageDataProvider
+	image = new Image(display, imageDataProvider);
+	imageDataAtZoom = image.getImageData(zoom);
+	boundsAtZoom = new Rectangle(0, 0, imageDataAtZoom.width, imageDataAtZoom.height);
+	bounds = image.getBounds();
+	image.dispose();
+	assertEquals(":d: Size of ImageData returned from Image.getImageData(int) method doesn't return matches with bounds in Pixel values.", scaleBounds(bounds, zoom, 100), boundsAtZoom);
+}
+
+public static Rectangle scaleBounds (Rectangle rect, int targetZoom, int currentZoom) {
+	float scaleFactor = ((float)targetZoom) / (float)currentZoom;
+	Rectangle returnRect = new Rectangle (0,0,0,0);
+	returnRect.x = Math.round (rect.x * scaleFactor);
+	returnRect.y = Math.round (rect.y * scaleFactor);
+	returnRect.width = Math.round (rect.width * scaleFactor);
+	returnRect.height = Math.round (rect.height * scaleFactor);
+	return returnRect;
+}
+
+@Test
 public void test_hashCode() {
 	Image image = null;
 	Image image1 = null;