org.eclipse.swt.graphics.Image.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.graphics.Image.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.graphics;

import java.io.*;

import org.eclipse.swt.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.gdip.*;
import org.eclipse.swt.internal.win32.*;

/**
 * Instances of this class are graphics which have been prepared
 * for display on a specific device. That is, they are ready
 * to paint using methods such as <code>GC.drawImage()</code>
 * and display on widgets with, for example, <code>Button.setImage()</code>.
 * <p>
 * If loaded from a file format that supports it, an
 * <code>Image</code> may have transparency, meaning that certain
 * pixels are specified as being transparent when drawn. Examples
 * of file formats that support transparency are GIF and PNG.
 * </p><p>
 * There are two primary ways to use <code>Images</code>.
 * The first is to load a graphic file from disk and create an
 * <code>Image</code> from it. This is done using an <code>Image</code>
 * constructor, for example:
 * <pre>
 *    Image i = new Image(device, "C:\\graphic.bmp");
 * </pre>
 * A graphic file may contain a color table specifying which
 * colors the image was intended to possess. In the above example,
 * these colors will be mapped to the closest available color in
 * SWT. It is possible to get more control over the mapping of
 * colors as the image is being created, using code of the form:
 * <pre>
 *    ImageData data = new ImageData("C:\\graphic.bmp");
 *    RGB[] rgbs = data.getRGBs();
 *    // At this point, rgbs contains specifications of all
 *    // the colors contained within this image. You may
 *    // allocate as many of these colors as you wish by
 *    // using the Color constructor Color(RGB), then
 *    // create the image:
 *    Image i = new Image(device, data);
 * </pre>
 * <p>
 * Applications which require even greater control over the image
 * loading process should use the support provided in class
 * <code>ImageLoader</code>.
 * </p><p>
 * Application code must explicitly invoke the <code>Image.dispose()</code>
 * method to release the operating system resources managed by each instance
 * when those instances are no longer required.
 * </p>
 *
 * @see Color
 * @see ImageData
 * @see ImageLoader
 * @see <a href="http://www.eclipse.org/swt/snippets/#image">Image snippets</a>
 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: GraphicsExample, ImageAnalyzer</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 */
public final class Image extends Resource implements Drawable {

    /**
     * specifies whether the receiver is a bitmap or an icon
     * (one of <code>SWT.BITMAP</code>, <code>SWT.ICON</code>)
     * <p>
     * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
     * public API. It is marked public only so that it can be shared
     * within the packages provided by SWT. It is not available on all
     * platforms and should never be accessed from application code.
     * </p>
     *
     * @noreference This field is not intended to be referenced by clients.
     */
    public int type;

    /**
     * the handle to the OS image resource
     * (Warning: This field is platform dependent)
     * <p>
     * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
     * public API. It is marked public only so that it can be shared
     * within the packages provided by SWT. It is not available on all
     * platforms and should never be accessed from application code.
     * </p>
     *
     * @noreference This field is not intended to be referenced by clients.
     */
    public long handle;

    /**
     * specifies the transparent pixel
     */
    int transparentPixel = -1, transparentColor = -1;

    /**
     * the GC which is drawing on the image
     */
    GC memGC;

    /**
     * the alpha data for the image
     */
    byte[] alphaData;

    /**
     * the global alpha value to be used for every pixel
     */
    int alpha = -1;

    /**
     * ImageFileNameProvider to provide file names at various Zoom levels
     */
    private ImageFileNameProvider imageFileNameProvider;

    /**
     * ImageDataProvider to provide ImageData at various Zoom levels
     */
    private ImageDataProvider imageDataProvider;

    /**
     * Style flag used to differentiate normal, gray-scale and disabled images based
     * on image data providers. Without this, a normal and a disabled image of the
     * same image data provider would be considered equal.
     */
    private int styleFlag = SWT.IMAGE_COPY;

    /**
     * Attribute to cache current device zoom level
     */
    private int currentDeviceZoom = 100;

    /**
     * width of the image
     */
    int width = -1;

    /**
     * height of the image
     */
    int height = -1;

    /**
     * specifies the default scanline padding
     */
    static final int DEFAULT_SCANLINE_PAD = 4;

    /**
     * Prevents uninitialized instances from being created outside the package.
     */
    Image(Device device) {
        super(device);
        currentDeviceZoom = DPIUtil.getDeviceZoom();
    }

    /**
     * Constructs an empty instance of this class with the
     * specified width and height. The result may be drawn upon
     * by creating a GC and using any of its drawing operations,
     * as shown in the following example:
     * <pre>
     *    Image i = new Image(device, width, height);
     *    GC gc = new GC(i);
     *    gc.drawRectangle(0, 0, 50, 50);
     *    gc.dispose();
     * </pre>
     * <p>
     * Note: Some platforms may have a limitation on the size
     * of image that can be created (size depends on width, height,
     * and depth). For example, Windows 95, 98, and ME do not allow
     * images larger than 16M.
     * </p>
     * <p>
     * You must dispose the image when it is no longer required.
     * </p>
     *
     * @param device the device on which to create the image
     * @param width the width of the new image
     * @param height the height of the new image
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_INVALID_ARGUMENT - if either the width or height is negative or zero</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     *
     * @see #dispose()
     */
    public Image(Device device, int width, int height) {
        super(device);
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        width = DPIUtil.autoScaleUp(width);
        height = DPIUtil.autoScaleUp(height);
        init(width, height);
        init();
    }

    /**
     * Constructs a new instance of this class based on the
     * provided image, with an appearance that varies depending
     * on the value of the flag. The possible flag values are:
     * <dl>
     * <dt><b>{@link SWT#IMAGE_COPY}</b></dt>
     * <dd>the result is an identical copy of srcImage</dd>
     * <dt><b>{@link SWT#IMAGE_DISABLE}</b></dt>
     * <dd>the result is a copy of srcImage which has a <em>disabled</em> look</dd>
     * <dt><b>{@link SWT#IMAGE_GRAY}</b></dt>
     * <dd>the result is a copy of srcImage which has a <em>gray scale</em> look</dd>
     * </dl>
     * <p>
     * You must dispose the image when it is no longer required.
     * </p>
     *
     * @param device the device on which to create the image
     * @param srcImage the image to use as the source
     * @param flag the style, either <code>IMAGE_COPY</code>, <code>IMAGE_DISABLE</code> or <code>IMAGE_GRAY</code>
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if srcImage is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the flag is not one of <code>IMAGE_COPY</code>, <code>IMAGE_DISABLE</code> or <code>IMAGE_GRAY</code></li>
     *    <li>ERROR_INVALID_ARGUMENT - if the image has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_INVALID_IMAGE - if the image is not a bitmap or an icon, or is otherwise in an invalid state</li>
     *    <li>ERROR_UNSUPPORTED_DEPTH - if the depth of the image is not supported</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     *
     * @see #dispose()
     */
    public Image(Device device, Image srcImage, int flag) {
        super(device);
        device = this.device;
        if (srcImage == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (srcImage.isDisposed())
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        Rectangle rect = srcImage.getBoundsInPixels();
        this.type = srcImage.type;
        this.imageDataProvider = srcImage.imageDataProvider;
        this.imageFileNameProvider = srcImage.imageFileNameProvider;
        this.styleFlag = srcImage.styleFlag | flag;
        this.currentDeviceZoom = srcImage.currentDeviceZoom;
        switch (flag) {
        case SWT.IMAGE_COPY: {
            switch (type) {
            case SWT.BITMAP:
                /* Get the HDC for the device */
                long hDC = device.internal_new_GC(null);

                /* Copy the bitmap */
                long hdcSource = OS.CreateCompatibleDC(hDC);
                long hdcDest = OS.CreateCompatibleDC(hDC);
                long hOldSrc = OS.SelectObject(hdcSource, srcImage.handle);
                BITMAP bm = new BITMAP();
                OS.GetObject(srcImage.handle, BITMAP.sizeof, bm);
                handle = OS.CreateCompatibleBitmap(hdcSource, rect.width,
                        bm.bmBits != 0 ? -rect.height : rect.height);
                if (handle == 0)
                    SWT.error(SWT.ERROR_NO_HANDLES);
                long hOldDest = OS.SelectObject(hdcDest, handle);
                OS.BitBlt(hdcDest, 0, 0, rect.width, rect.height, hdcSource, 0, 0, OS.SRCCOPY);
                OS.SelectObject(hdcSource, hOldSrc);
                OS.SelectObject(hdcDest, hOldDest);
                OS.DeleteDC(hdcSource);
                OS.DeleteDC(hdcDest);

                /* Release the HDC for the device */
                device.internal_dispose_GC(hDC, null);

                transparentPixel = srcImage.transparentPixel;
                alpha = srcImage.alpha;
                if (srcImage.alphaData != null) {
                    alphaData = new byte[srcImage.alphaData.length];
                    System.arraycopy(srcImage.alphaData, 0, alphaData, 0, alphaData.length);
                }
                break;
            case SWT.ICON:
                handle = OS.CopyImage(srcImage.handle, OS.IMAGE_ICON, rect.width, rect.height, 0);
                if (handle == 0)
                    SWT.error(SWT.ERROR_NO_HANDLES);
                break;
            default:
                SWT.error(SWT.ERROR_INVALID_IMAGE);
            }
            break;
        }
        case SWT.IMAGE_DISABLE: {
            ImageData data = srcImage.getImageDataAtCurrentZoom();
            PaletteData palette = data.palette;
            RGB[] rgbs = new RGB[3];
            rgbs[0] = device.getSystemColor(SWT.COLOR_BLACK).getRGB();
            rgbs[1] = device.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW).getRGB();
            rgbs[2] = device.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND).getRGB();
            ImageData newData = new ImageData(rect.width, rect.height, 8, new PaletteData(rgbs));
            newData.alpha = data.alpha;
            newData.alphaData = data.alphaData;
            newData.maskData = data.maskData;
            newData.maskPad = data.maskPad;
            if (data.transparentPixel != -1)
                newData.transparentPixel = 0;

            /* Convert the pixels. */
            int[] scanline = new int[rect.width];
            int[] maskScanline = null;
            ImageData mask = null;
            if (data.maskData != null)
                mask = data.getTransparencyMask();
            if (mask != null)
                maskScanline = new int[rect.width];
            int redMask = palette.redMask;
            int greenMask = palette.greenMask;
            int blueMask = palette.blueMask;
            int redShift = palette.redShift;
            int greenShift = palette.greenShift;
            int blueShift = palette.blueShift;
            for (int y = 0; y < rect.height; y++) {
                int offset = y * newData.bytesPerLine;
                data.getPixels(0, y, rect.width, scanline, 0);
                if (mask != null)
                    mask.getPixels(0, y, rect.width, maskScanline, 0);
                for (int x = 0; x < rect.width; x++) {
                    int pixel = scanline[x];
                    if (!((data.transparentPixel != -1 && pixel == data.transparentPixel)
                            || (mask != null && maskScanline[x] == 0))) {
                        int red, green, blue;
                        if (palette.isDirect) {
                            red = pixel & redMask;
                            red = (redShift < 0) ? red >>> -redShift : red << redShift;
                            green = pixel & greenMask;
                            green = (greenShift < 0) ? green >>> -greenShift : green << greenShift;
                            blue = pixel & blueMask;
                            blue = (blueShift < 0) ? blue >>> -blueShift : blue << blueShift;
                        } else {
                            red = palette.colors[pixel].red;
                            green = palette.colors[pixel].green;
                            blue = palette.colors[pixel].blue;
                        }
                        int intensity = red * red + green * green + blue * blue;
                        if (intensity < 98304) {
                            newData.data[offset] = (byte) 1;
                        } else {
                            newData.data[offset] = (byte) 2;
                        }
                    }
                    offset++;
                }
            }
            init(newData);
            break;
        }
        case SWT.IMAGE_GRAY: {
            ImageData data = srcImage.getImageDataAtCurrentZoom();
            PaletteData palette = data.palette;
            ImageData newData = data;
            if (!palette.isDirect) {
                /* Convert the palette entries to gray. */
                RGB[] rgbs = palette.getRGBs();
                for (int i = 0; i < rgbs.length; i++) {
                    if (data.transparentPixel != i) {
                        RGB color = rgbs[i];
                        int red = color.red;
                        int green = color.green;
                        int blue = color.blue;
                        int intensity = (red + red + green + green + green + green + green + blue) >> 3;
                        color.red = color.green = color.blue = intensity;
                    }
                }
                newData.palette = new PaletteData(rgbs);
            } else {
                /* Create a 8 bit depth image data with a gray palette. */
                RGB[] rgbs = new RGB[256];
                for (int i = 0; i < rgbs.length; i++) {
                    rgbs[i] = new RGB(i, i, i);
                }
                newData = new ImageData(rect.width, rect.height, 8, new PaletteData(rgbs));
                newData.alpha = data.alpha;
                newData.alphaData = data.alphaData;
                newData.maskData = data.maskData;
                newData.maskPad = data.maskPad;
                if (data.transparentPixel != -1)
                    newData.transparentPixel = 254;

                /* Convert the pixels. */
                int[] scanline = new int[rect.width];
                int redMask = palette.redMask;
                int greenMask = palette.greenMask;
                int blueMask = palette.blueMask;
                int redShift = palette.redShift;
                int greenShift = palette.greenShift;
                int blueShift = palette.blueShift;
                for (int y = 0; y < rect.height; y++) {
                    int offset = y * newData.bytesPerLine;
                    data.getPixels(0, y, rect.width, scanline, 0);
                    for (int x = 0; x < rect.width; x++) {
                        int pixel = scanline[x];
                        if (pixel != data.transparentPixel) {
                            int red = pixel & redMask;
                            red = (redShift < 0) ? red >>> -redShift : red << redShift;
                            int green = pixel & greenMask;
                            green = (greenShift < 0) ? green >>> -greenShift : green << greenShift;
                            int blue = pixel & blueMask;
                            blue = (blueShift < 0) ? blue >>> -blueShift : blue << blueShift;
                            int intensity = (red + red + green + green + green + green + green + blue) >> 3;
                            if (newData.transparentPixel == intensity)
                                intensity = 255;
                            newData.data[offset] = (byte) intensity;
                        } else {
                            newData.data[offset] = (byte) 254;
                        }
                        offset++;
                    }
                }
            }
            init(newData);
            break;
        }
        default:
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        init();
    }

    /**
     * Constructs an empty instance of this class with the
     * width and height of the specified rectangle. The result
     * may be drawn upon by creating a GC and using any of its
     * drawing operations, as shown in the following example:
     * <pre>
     *    Image i = new Image(device, boundsRectangle);
     *    GC gc = new GC(i);
     *    gc.drawRectangle(0, 0, 50, 50);
     *    gc.dispose();
     * </pre>
     * <p>
     * Note: Some platforms may have a limitation on the size
     * of image that can be created (size depends on width, height,
     * and depth). For example, Windows 95, 98, and ME do not allow
     * images larger than 16M.
     * </p>
     * <p>
     * You must dispose the image when it is no longer required.
     * </p>
     *
     * @param device the device on which to create the image
     * @param bounds a rectangle specifying the image's width and height (must not be null)
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if the bounds rectangle is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if either the rectangle's width or height is negative</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     *
     * @see #dispose()
     */
    public Image(Device device, Rectangle bounds) {
        super(device);
        if (bounds == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        bounds = DPIUtil.autoScaleUp(bounds);
        init(bounds.width, bounds.height);
        init();
    }

    /**
     * Constructs an instance of this class from the given
     * <code>ImageData</code>.
     * <p>
     * You must dispose the image when it is no longer required.
     * </p>
     *
     * @param device the device on which to create the image
     * @param data the image data to create the image from (must not be null)
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if the image data is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_UNSUPPORTED_DEPTH - if the depth of the ImageData is not supported</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     *
     * @see #dispose()
     */
    public Image(Device device, ImageData data) {
        super(device);
        if (data == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        data = DPIUtil.autoScaleUp(device, data);
        init(data);
        init();
    }

    /**
     * Constructs an instance of this class, whose type is
     * <code>SWT.ICON</code>, from the two given <code>ImageData</code>
     * objects. The two images must be the same size. Pixel transparency
     * in either image will be ignored.
     * <p>
     * The mask image should contain white wherever the icon is to be visible,
     * and black wherever the icon is to be transparent. In addition,
     * the source image should contain black wherever the icon is to be
     * transparent.
     * </p>
     * <p>
     * You must dispose the image when it is no longer required.
     * </p>
     *
     * @param device the device on which to create the icon
     * @param source the color data for the icon
     * @param mask the mask data for the icon
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if either the source or mask is null </li>
     *    <li>ERROR_INVALID_ARGUMENT - if source and mask are different sizes</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     *
     * @see #dispose()
     */
    public Image(Device device, ImageData source, ImageData mask) {
        super(device);
        if (source == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (mask == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (source.width != mask.width || source.height != mask.height) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        source = DPIUtil.autoScaleUp(device, source);
        mask = DPIUtil.autoScaleUp(device, mask);
        mask = ImageData.convertMask(mask);
        init(this.device, this, source, mask);
        init();
    }

    /**
     * Constructs an instance of this class by loading its representation
     * from the specified input stream. Throws an error if an error
     * occurs while loading the image, or if the result is an image
     * of an unsupported type.  Application code is still responsible
     * for closing the input stream.
     * <p>
     * This constructor is provided for convenience when loading a single
     * image only. If the stream contains multiple images, only the first
     * one will be loaded. To load multiple images, use
     * <code>ImageLoader.load()</code>.
     * </p><p>
     * This constructor may be used to load a resource as follows:
     * </p>
     * <pre>
     *     static Image loadImage (Display display, Class clazz, String string) {
     *          InputStream stream = clazz.getResourceAsStream (string);
     *          if (stream == null) return null;
     *          Image image = null;
     *          try {
     *               image = new Image (display, stream);
     *          } catch (SWTException ex) {
     *          } finally {
     *               try {
     *                    stream.close ();
     *               } catch (IOException ex) {}
     *          }
     *          return image;
     *     }
     * </pre>
     * <p>
     * You must dispose the image when it is no longer required.
     * </p>
     *
     * @param device the device on which to create the image
     * @param stream the input stream to load the image from
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if the stream is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_IO - if an IO error occurs while reading from the stream</li>
     *    <li>ERROR_INVALID_IMAGE - if the image stream contains invalid data </li>
     *    <li>ERROR_UNSUPPORTED_DEPTH - if the image stream describes an image with an unsupported depth</li>
     *    <li>ERROR_UNSUPPORTED_FORMAT - if the image stream contains an unrecognized format</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     *
     * @see #dispose()
     */
    public Image(Device device, InputStream stream) {
        super(device);
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        ImageData data = DPIUtil.autoScaleUp(device, new ImageData(stream));
        init(data);
        init();
    }

    /**
     * Constructs an instance of this class by loading its representation
     * from the file with the specified name. Throws an error if an error
     * occurs while loading the image, or if the result is an image
     * of an unsupported type.
     * <p>
     * This constructor is provided for convenience when loading
     * a single image only. If the specified file contains
     * multiple images, only the first one will be used.
     * <p>
     * You must dispose the image when it is no longer required.
     * </p>
     *
     * @param device the device on which to create the image
     * @param filename the name of the file to load the image from
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if the file name is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_IO - if an IO error occurs while reading from the file</li>
     *    <li>ERROR_INVALID_IMAGE - if the image file contains invalid data </li>
     *    <li>ERROR_UNSUPPORTED_DEPTH - if the image file describes an image with an unsupported depth</li>
     *    <li>ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     *
     * @see #dispose()
     */
    public Image(Device device, String filename) {
        super(device);
        if (filename == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        ImageData data = DPIUtil.autoScaleUp(device, new ImageData(filename));
        init(data);
        init();
    }

    /**
     * Constructs an instance of this class by loading its representation
     * from the file retrieved from the ImageFileNameProvider. Throws an
     * error if an error occurs while loading the image, or if the result
     * is an image of an unsupported type.
     * <p>
     * This constructor is provided for convenience for loading image as
     * per DPI level.
     *
     * @param device the device on which to create the image
     * @param imageFileNameProvider the ImageFileNameProvider object that is
     * to be used to get the file name
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if the ImageFileNameProvider is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the fileName provided by ImageFileNameProvider is null at 100% zoom</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_IO - if an IO error occurs while reading from the file</li>
     *    <li>ERROR_INVALID_IMAGE - if the image file contains invalid data </li>
     *    <li>ERROR_UNSUPPORTED_DEPTH - if the image file describes an image with an unsupported depth</li>
     *    <li>ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     * @since 3.104
     */
    public Image(Device device, ImageFileNameProvider imageFileNameProvider) {
        super(device);
        this.imageFileNameProvider = imageFileNameProvider;
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        boolean[] found = new boolean[1];
        String fileName = DPIUtil.validateAndGetImagePathAtZoom(imageFileNameProvider, currentDeviceZoom, found);
        if (found[0]) {
            initNative(fileName);
            if (this.handle == 0)
                init(new ImageData(fileName));
        } else {
            ImageData resizedData = DPIUtil.autoScaleUp(device, new ImageData(fileName));
            init(resizedData);
        }
        init();
    }

    /**
     * Constructs an instance of this class by loading its representation
     * from the ImageData retrieved from the ImageDataProvider. Throws an
     * error if an error occurs while loading the image, or if the result
     * is an image of an unsupported type.
     * <p>
     * This constructor is provided for convenience for loading image as
     * per DPI level.
     *
     * @param device the device on which to create the image
     * @param imageDataProvider the ImageDataProvider object that is
     * to be used to get the ImageData
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
     *    <li>ERROR_NULL_ARGUMENT - if the ImageDataProvider is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the ImageData provided by ImageDataProvider is null at 100% zoom</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_IO - if an IO error occurs while reading from the file</li>
     *    <li>ERROR_INVALID_IMAGE - if the image file contains invalid data </li>
     *    <li>ERROR_UNSUPPORTED_DEPTH - if the image file describes an image with an unsupported depth</li>
     *    <li>ERROR_UNSUPPORTED_FORMAT - if the image file contains an unrecognized format</li>
     * </ul>
     * @exception SWTError <ul>
     *    <li>ERROR_NO_HANDLES if a handle could not be obtained for image creation</li>
     * </ul>
     * @since 3.104
     */
    public Image(Device device, ImageDataProvider imageDataProvider) {
        super(device);
        this.imageDataProvider = imageDataProvider;
        currentDeviceZoom = DPIUtil.getDeviceZoom();
        boolean[] found = new boolean[1];
        ImageData data = DPIUtil.validateAndGetImageDataAtZoom(imageDataProvider, currentDeviceZoom, found);
        if (found[0]) {
            init(data);
        } else {
            ImageData resizedData = DPIUtil.autoScaleUp(device, data);
            init(resizedData);
        }
        init();
    }

    /**
     * Refresh the Image based on the zoom level, if required.
     *
     * @return true if image is refreshed
     */
    boolean refreshImageForZoom() {
        boolean refreshed = false;
        int deviceZoomLevel = DPIUtil.getDeviceZoom();
        if (imageFileNameProvider != null) {
            if (deviceZoomLevel != currentDeviceZoom) {
                boolean[] found = new boolean[1];
                String filename = DPIUtil.validateAndGetImagePathAtZoom(imageFileNameProvider, deviceZoomLevel,
                        found);
                /* Avoid re-creating the fall-back image, when current zoom is already 100% */
                if (found[0] || currentDeviceZoom != 100) {
                    /* Release current native resources */
                    destroy();
                    initNative(filename);
                    if (this.handle == 0)
                        init(new ImageData(filename));
                    init();
                    refreshed = true;
                }
                if (!found[0]) {
                    /* Release current native resources */
                    destroy();
                    ImageData resizedData = DPIUtil.autoScaleUp(device, new ImageData(filename));
                    init(resizedData);
                    init();
                    refreshed = true;
                }
                currentDeviceZoom = deviceZoomLevel;
            }
        } else if (imageDataProvider != null) {
            if (deviceZoomLevel != currentDeviceZoom) {
                boolean[] found = new boolean[1];
                ImageData data = DPIUtil.validateAndGetImageDataAtZoom(imageDataProvider, deviceZoomLevel, found);
                /* Avoid re-creating the fall-back image, when current zoom is already 100% */
                if (found[0] || currentDeviceZoom != 100) {
                    /* Release current native resources */
                    destroy();
                    init(data);
                    init();
                    refreshed = true;
                }
                if (!found[0]) {
                    /* Release current native resources */
                    destroy();
                    ImageData resizedData = DPIUtil.autoScaleUp(device, data);
                    init(resizedData);
                    init();
                    refreshed = true;
                }
                currentDeviceZoom = deviceZoomLevel;
            }
        } else {
            if (deviceZoomLevel != currentDeviceZoom) {
                ImageData data = getImageDataAtCurrentZoom();
                destroy();
                ImageData resizedData = DPIUtil.autoScaleImageData(device, data, deviceZoomLevel,
                        currentDeviceZoom);
                init(resizedData);
                init();
                refreshed = true;
                currentDeviceZoom = deviceZoomLevel;
            }
        }
        return refreshed;
    }

    void initNative(String filename) {
        device.checkGDIP();
        boolean gdip = true;
        /*
        * Bug in GDI+.  For some reason, Bitmap.LockBits() segment faults
        * when loading GIF files in 64-bit Windows.  The fix is to not use
        * GDI+ image loading in this case.
        */
        if (gdip && C.PTR_SIZEOF == 8 && filename.toLowerCase().endsWith(".gif"))
            gdip = false;
        /*
        * Bug in GDI+. Bitmap.LockBits() fails to load GIF files in
        * Windows 7 when the image has a position offset in the first frame.
        * The fix is to not use GDI+ image loading in this case.
        */
        if (OS.WIN32_VERSION >= OS.VERSION(6, 1) && filename.toLowerCase().endsWith(".gif"))
            gdip = false;
        if (gdip) {
            int length = filename.length();
            char[] chars = new char[length + 1];
            filename.getChars(0, length, chars, 0);
            long bitmap = Gdip.Bitmap_new(chars, false);
            if (bitmap != 0) {
                int error = SWT.ERROR_NO_HANDLES;
                int status = Gdip.Image_GetLastStatus(bitmap);
                if (status == 0) {
                    if (filename.toLowerCase().endsWith(".ico")) {
                        this.type = SWT.ICON;
                        long[] hicon = new long[1];
                        status = Gdip.Bitmap_GetHICON(bitmap, hicon);
                        this.handle = hicon[0];
                    } else {
                        this.type = SWT.BITMAP;
                        int width = Gdip.Image_GetWidth(bitmap);
                        int height = Gdip.Image_GetHeight(bitmap);
                        int pixelFormat = Gdip.Image_GetPixelFormat(bitmap);
                        switch (pixelFormat) {
                        case Gdip.PixelFormat16bppRGB555:
                        case Gdip.PixelFormat16bppRGB565:
                            this.handle = createDIB(width, height, 16);
                            break;
                        case Gdip.PixelFormat24bppRGB:
                        case Gdip.PixelFormat32bppCMYK:
                            this.handle = createDIB(width, height, 24);
                            break;
                        case Gdip.PixelFormat32bppRGB:
                            // These will lose either precision or transparency
                        case Gdip.PixelFormat16bppGrayScale:
                        case Gdip.PixelFormat48bppRGB:
                        case Gdip.PixelFormat32bppPARGB:
                        case Gdip.PixelFormat64bppARGB:
                        case Gdip.PixelFormat64bppPARGB:
                            this.handle = createDIB(width, height, 32);
                            break;
                        }
                        if (this.handle != 0) {
                            /*
                            * This performs better than getting the bits with Bitmap.LockBits(),
                            * but it cannot be used when there is transparency.
                            */
                            long hDC = device.internal_new_GC(null);
                            long srcHDC = OS.CreateCompatibleDC(hDC);
                            long oldSrcBitmap = OS.SelectObject(srcHDC, this.handle);
                            long graphics = Gdip.Graphics_new(srcHDC);
                            if (graphics != 0) {
                                Rect rect = new Rect();
                                rect.Width = width;
                                rect.Height = height;
                                status = Gdip.Graphics_DrawImage(graphics, bitmap, rect, 0, 0, width, height,
                                        Gdip.UnitPixel, 0, 0, 0);
                                if (status != 0) {
                                    error = SWT.ERROR_INVALID_IMAGE;
                                    OS.DeleteObject(handle);
                                    this.handle = 0;
                                }
                                Gdip.Graphics_delete(graphics);
                            }
                            OS.SelectObject(srcHDC, oldSrcBitmap);
                            OS.DeleteDC(srcHDC);
                            device.internal_dispose_GC(hDC, null);
                        } else {
                            long lockedBitmapData = Gdip.BitmapData_new();
                            if (lockedBitmapData != 0) {
                                status = Gdip.Bitmap_LockBits(bitmap, 0, 0, pixelFormat, lockedBitmapData);
                                if (status == 0) {
                                    BitmapData bitmapData = new BitmapData();
                                    Gdip.MoveMemory(bitmapData, lockedBitmapData);
                                    int stride = bitmapData.Stride;
                                    long pixels = bitmapData.Scan0;
                                    int depth = 0, scanlinePad = 4, transparentPixel = -1;
                                    switch (bitmapData.PixelFormat) {
                                    case Gdip.PixelFormat1bppIndexed:
                                        depth = 1;
                                        break;
                                    case Gdip.PixelFormat4bppIndexed:
                                        depth = 4;
                                        break;
                                    case Gdip.PixelFormat8bppIndexed:
                                        depth = 8;
                                        break;
                                    case Gdip.PixelFormat16bppARGB1555:
                                    case Gdip.PixelFormat16bppRGB555:
                                    case Gdip.PixelFormat16bppRGB565:
                                        depth = 16;
                                        break;
                                    case Gdip.PixelFormat24bppRGB:
                                        depth = 24;
                                        break;
                                    case Gdip.PixelFormat32bppRGB:
                                    case Gdip.PixelFormat32bppARGB:
                                        depth = 32;
                                        break;
                                    }
                                    if (depth != 0) {
                                        PaletteData paletteData = null;
                                        switch (bitmapData.PixelFormat) {
                                        case Gdip.PixelFormat1bppIndexed:
                                        case Gdip.PixelFormat4bppIndexed:
                                        case Gdip.PixelFormat8bppIndexed:
                                            int paletteSize = Gdip.Image_GetPaletteSize(bitmap);
                                            long hHeap = OS.GetProcessHeap();
                                            long palette = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, paletteSize);
                                            if (palette == 0)
                                                SWT.error(SWT.ERROR_NO_HANDLES);
                                            Gdip.Image_GetPalette(bitmap, palette, paletteSize);
                                            ColorPalette colorPalette = new ColorPalette();
                                            Gdip.MoveMemory(colorPalette, palette, ColorPalette.sizeof);
                                            int[] entries = new int[colorPalette.Count];
                                            OS.MoveMemory(entries, palette + 8, entries.length * 4);
                                            OS.HeapFree(hHeap, 0, palette);
                                            RGB[] rgbs = new RGB[colorPalette.Count];
                                            paletteData = new PaletteData(rgbs);
                                            for (int i = 0; i < entries.length; i++) {
                                                if (((entries[i] >> 24) & 0xFF) == 0
                                                        && (colorPalette.Flags & Gdip.PaletteFlagsHasAlpha) != 0) {
                                                    transparentPixel = i;
                                                }
                                                rgbs[i] = new RGB(((entries[i] & 0xFF0000) >> 16),
                                                        ((entries[i] & 0xFF00) >> 8), ((entries[i] & 0xFF) >> 0));
                                            }
                                            break;
                                        case Gdip.PixelFormat16bppARGB1555:
                                        case Gdip.PixelFormat16bppRGB555:
                                            paletteData = new PaletteData(0x7C00, 0x3E0, 0x1F);
                                            break;
                                        case Gdip.PixelFormat16bppRGB565:
                                            paletteData = new PaletteData(0xF800, 0x7E0, 0x1F);
                                            break;
                                        case Gdip.PixelFormat24bppRGB:
                                            paletteData = new PaletteData(0xFF, 0xFF00, 0xFF0000);
                                            break;
                                        case Gdip.PixelFormat32bppRGB:
                                        case Gdip.PixelFormat32bppARGB:
                                            paletteData = new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
                                            break;
                                        }
                                        byte[] data = new byte[stride * height], alphaData = null;
                                        OS.MoveMemory(data, pixels, data.length);
                                        switch (bitmapData.PixelFormat) {
                                        case Gdip.PixelFormat16bppARGB1555:
                                            alphaData = new byte[width * height];
                                            for (int i = 1, j = 0; i < data.length; i += 2, j++) {
                                                alphaData[j] = (byte) ((data[i] & 0x80) != 0 ? 255 : 0);
                                            }
                                            break;
                                        case Gdip.PixelFormat32bppARGB:
                                            alphaData = new byte[width * height];
                                            for (int i = 3, j = 0; i < data.length; i += 4, j++) {
                                                alphaData[j] = data[i];
                                            }
                                            break;
                                        }
                                        ImageData img = new ImageData(width, height, depth, paletteData,
                                                scanlinePad, data);
                                        img.transparentPixel = transparentPixel;
                                        img.alphaData = alphaData;
                                        init(img);
                                    }
                                    Gdip.Bitmap_UnlockBits(bitmap, lockedBitmapData);
                                } else {
                                    error = SWT.ERROR_INVALID_IMAGE;
                                }
                                Gdip.BitmapData_delete(lockedBitmapData);
                            }
                        }
                    }
                }
                Gdip.Bitmap_delete(bitmap);
                if (status == 0) {
                    if (this.handle == 0)
                        SWT.error(error);
                }
            }
        }
    }

    long[] createGdipImage() {
        switch (type) {
        case SWT.BITMAP: {
            if (alpha != -1 || alphaData != null || transparentPixel != -1) {
                BITMAP bm = new BITMAP();
                OS.GetObject(handle, BITMAP.sizeof, bm);
                int imgWidth = bm.bmWidth;
                int imgHeight = bm.bmHeight;
                long hDC = device.internal_new_GC(null);
                long srcHdc = OS.CreateCompatibleDC(hDC);
                long oldSrcBitmap = OS.SelectObject(srcHdc, handle);
                long memHdc = OS.CreateCompatibleDC(hDC);
                long memDib = createDIB(imgWidth, imgHeight, 32);
                if (memDib == 0)
                    SWT.error(SWT.ERROR_NO_HANDLES);
                long oldMemBitmap = OS.SelectObject(memHdc, memDib);
                BITMAP dibBM = new BITMAP();
                OS.GetObject(memDib, BITMAP.sizeof, dibBM);
                int sizeInBytes = dibBM.bmWidthBytes * dibBM.bmHeight;
                OS.BitBlt(memHdc, 0, 0, imgWidth, imgHeight, srcHdc, 0, 0, OS.SRCCOPY);
                byte red = 0, green = 0, blue = 0;
                if (transparentPixel != -1) {
                    if (bm.bmBitsPixel <= 8) {
                        byte[] color = new byte[4];
                        OS.GetDIBColorTable(srcHdc, transparentPixel, 1, color);
                        blue = color[0];
                        green = color[1];
                        red = color[2];
                    } else {
                        switch (bm.bmBitsPixel) {
                        case 16:
                            int blueMask = 0x1F;
                            int blueShift = ImageData.getChannelShift(blueMask);
                            byte[] blues = ImageData.ANY_TO_EIGHT[ImageData.getChannelWidth(blueMask, blueShift)];
                            blue = blues[(transparentPixel & blueMask) >> blueShift];
                            int greenMask = 0x3E0;
                            int greenShift = ImageData.getChannelShift(greenMask);
                            byte[] greens = ImageData.ANY_TO_EIGHT[ImageData.getChannelWidth(greenMask,
                                    greenShift)];
                            green = greens[(transparentPixel & greenMask) >> greenShift];
                            int redMask = 0x7C00;
                            int redShift = ImageData.getChannelShift(redMask);
                            byte[] reds = ImageData.ANY_TO_EIGHT[ImageData.getChannelWidth(redMask, redShift)];
                            red = reds[(transparentPixel & redMask) >> redShift];
                            break;
                        case 24:
                            blue = (byte) ((transparentPixel & 0xFF0000) >> 16);
                            green = (byte) ((transparentPixel & 0xFF00) >> 8);
                            red = (byte) (transparentPixel & 0xFF);
                            break;
                        case 32:
                            blue = (byte) ((transparentPixel & 0xFF000000) >>> 24);
                            green = (byte) ((transparentPixel & 0xFF0000) >> 16);
                            red = (byte) ((transparentPixel & 0xFF00) >> 8);
                            break;
                        }
                    }
                }
                OS.SelectObject(srcHdc, oldSrcBitmap);
                OS.SelectObject(memHdc, oldMemBitmap);
                OS.DeleteObject(srcHdc);
                OS.DeleteObject(memHdc);
                byte[] srcData = new byte[sizeInBytes];
                OS.MoveMemory(srcData, dibBM.bmBits, sizeInBytes);
                OS.DeleteObject(memDib);
                device.internal_dispose_GC(hDC, null);
                if (alpha != -1) {
                    for (int y = 0, dp = 0; y < imgHeight; ++y) {
                        for (int x = 0; x < imgWidth; ++x) {
                            srcData[dp + 3] = (byte) alpha;
                            dp += 4;
                        }
                    }
                } else if (alphaData != null) {
                    for (int y = 0, dp = 0, ap = 0; y < imgHeight; ++y) {
                        for (int x = 0; x < imgWidth; ++x) {
                            srcData[dp + 3] = alphaData[ap++];
                            dp += 4;
                        }
                    }
                } else if (transparentPixel != -1) {
                    for (int y = 0, dp = 0; y < imgHeight; ++y) {
                        for (int x = 0; x < imgWidth; ++x) {
                            if (srcData[dp] == blue && srcData[dp + 1] == green && srcData[dp + 2] == red) {
                                srcData[dp + 3] = (byte) 0;
                            } else {
                                srcData[dp + 3] = (byte) 0xFF;
                            }
                            dp += 4;
                        }
                    }
                }
                long hHeap = OS.GetProcessHeap();
                long pixels = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, srcData.length);
                if (pixels == 0)
                    SWT.error(SWT.ERROR_NO_HANDLES);
                OS.MoveMemory(pixels, srcData, sizeInBytes);
                return new long[] {
                        Gdip.Bitmap_new(imgWidth, imgHeight, dibBM.bmWidthBytes, Gdip.PixelFormat32bppARGB, pixels),
                        pixels };
            }
            return new long[] { Gdip.Bitmap_new(handle, 0), 0 };
        }
        case SWT.ICON: {
            /*
            * Bug in GDI+. Creating a new GDI+ Bitmap from a HICON segment faults
            * when the icon width is bigger than the icon height.  The fix is to
            * detect this and create a PixelFormat32bppARGB image instead.
            */
            ICONINFO iconInfo = new ICONINFO();
            OS.GetIconInfo(handle, iconInfo);
            long hBitmap = iconInfo.hbmColor;
            if (hBitmap == 0)
                hBitmap = iconInfo.hbmMask;
            BITMAP bm = new BITMAP();
            OS.GetObject(hBitmap, BITMAP.sizeof, bm);
            int imgWidth = bm.bmWidth;
            int imgHeight = hBitmap == iconInfo.hbmMask ? bm.bmHeight / 2 : bm.bmHeight;
            long img = 0, pixels = 0;
            /*
            * Bug in GDI+.  Bitmap_new() segments fault if the image width
            * is greater than the image height.
            *
            * Note that it also fails to generated an appropriate alpha
            * channel when the icon depth is 32.
            */
            if (imgWidth > imgHeight || bm.bmBitsPixel == 32) {
                long hDC = device.internal_new_GC(null);
                long srcHdc = OS.CreateCompatibleDC(hDC);
                long oldSrcBitmap = OS.SelectObject(srcHdc, hBitmap);
                long memHdc = OS.CreateCompatibleDC(hDC);
                long memDib = createDIB(imgWidth, imgHeight, 32);
                if (memDib == 0)
                    SWT.error(SWT.ERROR_NO_HANDLES);
                long oldMemBitmap = OS.SelectObject(memHdc, memDib);
                BITMAP dibBM = new BITMAP();
                OS.GetObject(memDib, BITMAP.sizeof, dibBM);
                OS.BitBlt(memHdc, 0, 0, imgWidth, imgHeight, srcHdc, 0, hBitmap == iconInfo.hbmMask ? imgHeight : 0,
                        OS.SRCCOPY);
                OS.SelectObject(memHdc, oldMemBitmap);
                OS.DeleteObject(memHdc);
                byte[] srcData = new byte[dibBM.bmWidthBytes * dibBM.bmHeight];
                OS.MoveMemory(srcData, dibBM.bmBits, srcData.length);
                OS.DeleteObject(memDib);
                OS.SelectObject(srcHdc, iconInfo.hbmMask);
                for (int y = 0, dp = 3; y < imgHeight; ++y) {
                    for (int x = 0; x < imgWidth; ++x) {
                        if (srcData[dp] == 0) {
                            if (OS.GetPixel(srcHdc, x, y) != 0) {
                                srcData[dp] = (byte) 0;
                            } else {
                                srcData[dp] = (byte) 0xFF;
                            }
                        }
                        dp += 4;
                    }
                }
                OS.SelectObject(srcHdc, oldSrcBitmap);
                OS.DeleteObject(srcHdc);
                device.internal_dispose_GC(hDC, null);
                long hHeap = OS.GetProcessHeap();
                pixels = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, srcData.length);
                if (pixels == 0)
                    SWT.error(SWT.ERROR_NO_HANDLES);
                OS.MoveMemory(pixels, srcData, srcData.length);
                img = Gdip.Bitmap_new(imgWidth, imgHeight, dibBM.bmWidthBytes, Gdip.PixelFormat32bppARGB, pixels);
            } else {
                img = Gdip.Bitmap_new(handle);
            }
            if (iconInfo.hbmColor != 0)
                OS.DeleteObject(iconInfo.hbmColor);
            if (iconInfo.hbmMask != 0)
                OS.DeleteObject(iconInfo.hbmMask);
            return new long[] { img, pixels };
        }
        default:
            SWT.error(SWT.ERROR_INVALID_IMAGE);
        }
        return null;
    }

    @Override
    void destroy() {
        if (memGC != null)
            memGC.dispose();
        if (type == SWT.ICON) {
            OS.DestroyIcon(handle);
        } else {
            OS.DeleteObject(handle);
        }
        handle = 0;
        memGC = null;
    }

    /**
     * Compares the argument to the receiver, and returns true
     * if they represent the <em>same</em> object using a class
     * specific comparison.
     *
     * @param object the object to compare with this object
     * @return <code>true</code> if the object is the same as this object and <code>false</code> otherwise
     *
     * @see #hashCode
     */
    @Override
    public boolean equals(Object object) {
        if (object == this)
            return true;
        if (!(object instanceof Image))
            return false;
        Image image = (Image) object;
        if (device != image.device || transparentPixel != image.transparentPixel)
            return false;
        if (imageDataProvider != null && image.imageDataProvider != null) {
            return (styleFlag == image.styleFlag) && imageDataProvider.equals(image.imageDataProvider);
        } else if (imageFileNameProvider != null && image.imageFileNameProvider != null) {
            return (styleFlag == image.styleFlag) && imageFileNameProvider.equals(image.imageFileNameProvider);
        } else {
            return handle == image.handle;
        }
    }

    /**
     * Returns the color to which to map the transparent pixel, or null if
     * the receiver has no transparent pixel.
     * <p>
     * There are certain uses of Images that do not support transparency
     * (for example, setting an image into a button or label). In these cases,
     * it may be desired to simulate transparency by using the background
     * color of the widget to paint the transparent pixels of the image.
     * Use this method to check which color will be used in these cases
     * in place of transparency. This value may be set with setBackground().
     * <p>
     *
     * @return the background color of the image, or null if there is no transparency in the image
     *
     * @exception SWTException <ul>
     *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
     * </ul>
     */
    public Color getBackground() {
        if (isDisposed())
            SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
        if (transparentPixel == -1)
            return null;

        /* Get the HDC for the device */
        long hDC = device.internal_new_GC(null);

        /* Compute the background color */
        BITMAP bm = new BITMAP();
        OS.GetObject(handle, BITMAP.sizeof, bm);
        long hdcMem = OS.CreateCompatibleDC(hDC);
        long hOldObject = OS.SelectObject(hdcMem, handle);
        int red = 0, green = 0, blue = 0;
        if (bm.bmBitsPixel <= 8) {
            byte[] color = new byte[4];
            OS.GetDIBColorTable(hdcMem, transparentPixel, 1, color);
            blue = color[0] & 0xFF;
            green = color[1] & 0xFF;
            red = color[2] & 0xFF;
        } else {
            switch (bm.bmBitsPixel) {
            case 16:
                blue = (transparentPixel & 0x1F) << 3;
                green = (transparentPixel & 0x3E0) >> 2;
                red = (transparentPixel & 0x7C00) >> 7;
                break;
            case 24:
                blue = (transparentPixel & 0xFF0000) >> 16;
                green = (transparentPixel & 0xFF00) >> 8;
                red = transparentPixel & 0xFF;
                break;
            case 32:
                blue = (transparentPixel & 0xFF000000) >>> 24;
                green = (transparentPixel & 0xFF0000) >> 16;
                red = (transparentPixel & 0xFF00) >> 8;
                break;
            default:
                return null;
            }
        }
        OS.SelectObject(hdcMem, hOldObject);
        OS.DeleteDC(hdcMem);

        /* Release the HDC for the device */
        device.internal_dispose_GC(hDC, null);
        return Color.win32_new(device, (blue << 16) | (green << 8) | red);
    }

    /**
     * Returns the bounds of the receiver. The rectangle will always
     * have x and y values of 0, and the width and height of the
     * image.
     *
     * @return a rectangle specifying the image's bounds in points.
     *
     * @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>
     */
    public Rectangle getBounds() {
        if (isDisposed())
            SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
        return getBounds(100);
    }

    Rectangle getBounds(int zoom) {
        Rectangle bounds = getBoundsInPixels();
        if (bounds != null && zoom != currentDeviceZoom) {
            bounds = DPIUtil.autoScaleBounds(bounds, zoom, currentDeviceZoom);
        }
        return bounds;
    }

    /**
     * Returns the bounds of the receiver. The rectangle will always
     * have x and y values of 0, and the width and height of the
     * image in pixels.
     *
     * @return a rectangle specifying the image's bounds in pixels.
     *
     * @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.105
     * @deprecated This API doesn't serve the purpose in an environment having
     *             multiple monitors with different DPIs, hence deprecated.
     */
    @Deprecated
    public Rectangle getBoundsInPixels() {
        if (isDisposed())
            SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
        if (width != -1 && height != -1) {
            return new Rectangle(0, 0, width, height);
        }
        switch (type) {
        case SWT.BITMAP:
            BITMAP bm = new BITMAP();
            OS.GetObject(handle, BITMAP.sizeof, bm);
            return new Rectangle(0, 0, width = bm.bmWidth, height = bm.bmHeight);
        case SWT.ICON:
            ICONINFO info = new ICONINFO();
            OS.GetIconInfo(handle, info);
            long hBitmap = info.hbmColor;
            if (hBitmap == 0)
                hBitmap = info.hbmMask;
            bm = new BITMAP();
            OS.GetObject(hBitmap, BITMAP.sizeof, bm);
            if (hBitmap == info.hbmMask)
                bm.bmHeight /= 2;
            if (info.hbmColor != 0)
                OS.DeleteObject(info.hbmColor);
            if (info.hbmMask != 0)
                OS.DeleteObject(info.hbmMask);
            return new Rectangle(0, 0, width = bm.bmWidth, height = bm.bmHeight);
        default:
            SWT.error(SWT.ERROR_INVALID_IMAGE);
            return null;
        }
    }

    /**
     * Returns an <code>ImageData</code> based on the receiver.
     * Modifications made to this <code>ImageData</code> will not
     * affect the Image.
     *
     * @return an <code>ImageData</code> containing the image's data and
     *         attributes at 100% 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
     */
    public ImageData getImageData() {
        if (isDisposed())
            SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
        return getImageData(100);
    }

    /**
     * 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 point). 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);

        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.autoScaleImageData(device, data, zoom, 100);
        } 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.autoScaleImageData(device, new ImageData(fileName), zoom, 100);
        } else {
            return DPIUtil.autoScaleImageData(device, getImageDataAtCurrentZoom(), zoom, currentDeviceZoom);
        }
    }

    /**
     * Returns an <code>ImageData</code> based on the receiver.
     * Modifications made to this <code>ImageData</code> will not
     * affect the Image.
     *
     * @return an <code>ImageData</code> containing the image's data
     * and attributes at the current 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
     * @deprecated This API doesn't serve the purpose in an environment having
     *             multiple monitors with different DPIs, hence deprecated. Use
     *             {@link #getImageData(int)} instead.
     */
    @Deprecated
    public ImageData getImageDataAtCurrentZoom() {
        if (isDisposed())
            SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
        BITMAP bm;
        int depth, width, height;
        switch (type) {
        case SWT.ICON: {
            ICONINFO info = new ICONINFO();
            OS.GetIconInfo(handle, info);
            /* Get the basic BITMAP information */
            long hBitmap = info.hbmColor;
            if (hBitmap == 0)
                hBitmap = info.hbmMask;
            bm = new BITMAP();
            OS.GetObject(hBitmap, BITMAP.sizeof, bm);
            depth = bm.bmPlanes * bm.bmBitsPixel;
            width = bm.bmWidth;
            if (hBitmap == info.hbmMask)
                bm.bmHeight /= 2;
            height = bm.bmHeight;
            int numColors = 0;
            if (depth <= 8)
                numColors = 1 << depth;
            /* Create the BITMAPINFO */
            BITMAPINFOHEADER bmiHeader = new BITMAPINFOHEADER();
            bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
            bmiHeader.biWidth = width;
            bmiHeader.biHeight = -height;
            bmiHeader.biPlanes = 1;
            bmiHeader.biBitCount = (short) depth;
            bmiHeader.biCompression = OS.BI_RGB;
            byte[] bmi = new byte[BITMAPINFOHEADER.sizeof + numColors * 4];
            OS.MoveMemory(bmi, bmiHeader, BITMAPINFOHEADER.sizeof);

            /* Get the HDC for the device */
            long hDC = device.internal_new_GC(null);

            /* Create the DC and select the bitmap */
            long hBitmapDC = OS.CreateCompatibleDC(hDC);
            long hOldBitmap = OS.SelectObject(hBitmapDC, hBitmap);
            /* Find the size of the image and allocate data */
            int imageSize;
            /* Call with null lpBits to get the image size */
            OS.GetDIBits(hBitmapDC, hBitmap, 0, height, null, bmi, OS.DIB_RGB_COLORS);
            OS.MoveMemory(bmiHeader, bmi, BITMAPINFOHEADER.sizeof);
            imageSize = bmiHeader.biSizeImage;
            byte[] data = new byte[imageSize];
            /* Get the bitmap data */
            OS.GetDIBits(hBitmapDC, hBitmap, 0, height, data, bmi, OS.DIB_RGB_COLORS);
            /* Calculate the palette */
            PaletteData palette = null;
            if (depth <= 8) {
                RGB[] rgbs = new RGB[numColors];
                int srcIndex = 40;
                for (int i = 0; i < numColors; i++) {
                    rgbs[i] = new RGB(bmi[srcIndex + 2] & 0xFF, bmi[srcIndex + 1] & 0xFF, bmi[srcIndex] & 0xFF);
                    srcIndex += 4;
                }
                palette = new PaletteData(rgbs);
            } else if (depth == 16) {
                palette = new PaletteData(0x7C00, 0x3E0, 0x1F);
            } else if (depth == 24) {
                palette = new PaletteData(0xFF, 0xFF00, 0xFF0000);
            } else if (depth == 32) {
                palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
            } else {
                SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
            }

            /* Do the mask */
            byte[] maskData = null;
            if (info.hbmColor == 0) {
                /* Do the bottom half of the mask */
                maskData = new byte[imageSize];
                OS.GetDIBits(hBitmapDC, hBitmap, height, height, maskData, bmi, OS.DIB_RGB_COLORS);
            } else {
                /* Do the entire mask */
                /* Create the BITMAPINFO */
                bmiHeader = new BITMAPINFOHEADER();
                bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
                bmiHeader.biWidth = width;
                bmiHeader.biHeight = -height;
                bmiHeader.biPlanes = 1;
                bmiHeader.biBitCount = 1;
                bmiHeader.biCompression = OS.BI_RGB;
                bmi = new byte[BITMAPINFOHEADER.sizeof + 8];
                OS.MoveMemory(bmi, bmiHeader, BITMAPINFOHEADER.sizeof);

                /* First color black, second color white */
                int offset = BITMAPINFOHEADER.sizeof;
                bmi[offset + 4] = bmi[offset + 5] = bmi[offset + 6] = (byte) 0xFF;
                bmi[offset + 7] = 0;
                OS.SelectObject(hBitmapDC, info.hbmMask);
                /* Call with null lpBits to get the image size */
                OS.GetDIBits(hBitmapDC, info.hbmMask, 0, height, null, bmi, OS.DIB_RGB_COLORS);
                OS.MoveMemory(bmiHeader, bmi, BITMAPINFOHEADER.sizeof);
                imageSize = bmiHeader.biSizeImage;
                maskData = new byte[imageSize];
                OS.GetDIBits(hBitmapDC, info.hbmMask, 0, height, maskData, bmi, OS.DIB_RGB_COLORS);
                /* Loop to invert the mask */
                for (int i = 0; i < maskData.length; i++) {
                    maskData[i] ^= -1;
                }
                /* Make sure mask scanlinePad is 2 */
                int maskPad;
                int bpl = imageSize / height;
                for (maskPad = 1; maskPad < 128; maskPad++) {
                    int calcBpl = (((width + 7) / 8) + (maskPad - 1)) / maskPad * maskPad;
                    if (calcBpl == bpl)
                        break;
                }
                maskData = ImageData.convertPad(maskData, width, height, 1, maskPad, 2);
            }
            /* Clean up */
            OS.SelectObject(hBitmapDC, hOldBitmap);
            OS.DeleteDC(hBitmapDC);

            /* Release the HDC for the device */
            device.internal_dispose_GC(hDC, null);

            if (info.hbmColor != 0)
                OS.DeleteObject(info.hbmColor);
            if (info.hbmMask != 0)
                OS.DeleteObject(info.hbmMask);
            /* Construct and return the ImageData */
            ImageData imageData = new ImageData(width, height, depth, palette, 4, data);
            imageData.maskData = maskData;
            imageData.maskPad = 2;
            return imageData;
        }
        case SWT.BITMAP: {
            /* Get the basic BITMAP information */
            bm = new BITMAP();
            OS.GetObject(handle, BITMAP.sizeof, bm);
            depth = bm.bmPlanes * bm.bmBitsPixel;
            width = bm.bmWidth;
            height = bm.bmHeight;
            /* Find out whether this is a DIB or a DDB. */
            boolean isDib = (bm.bmBits != 0);
            /* Get the HDC for the device */
            long hDC = device.internal_new_GC(null);
            DIBSECTION dib = null;
            if (isDib) {
                dib = new DIBSECTION();
                OS.GetObject(handle, DIBSECTION.sizeof, dib);
            }
            /* Calculate number of colors */
            int numColors = 0;
            if (depth <= 8) {
                if (isDib) {
                    numColors = dib.biClrUsed;
                } else {
                    numColors = 1 << depth;
                }
            }
            /* Create the BITMAPINFO */
            byte[] bmi = null;
            BITMAPINFOHEADER bmiHeader = null;
            if (!isDib) {
                bmiHeader = new BITMAPINFOHEADER();
                bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
                bmiHeader.biWidth = width;
                bmiHeader.biHeight = -height;
                bmiHeader.biPlanes = 1;
                bmiHeader.biBitCount = (short) depth;
                bmiHeader.biCompression = OS.BI_RGB;
                bmi = new byte[BITMAPINFOHEADER.sizeof + numColors * 4];
                OS.MoveMemory(bmi, bmiHeader, BITMAPINFOHEADER.sizeof);
            }

            /* Create the DC and select the bitmap */
            long hBitmapDC = OS.CreateCompatibleDC(hDC);
            long hOldBitmap = OS.SelectObject(hBitmapDC, handle);
            /* Find the size of the image and allocate data */
            int imageSize;
            if (isDib) {
                imageSize = dib.biSizeImage;
            } else {
                /* Call with null lpBits to get the image size */
                OS.GetDIBits(hBitmapDC, handle, 0, height, null, bmi, OS.DIB_RGB_COLORS);
                OS.MoveMemory(bmiHeader, bmi, BITMAPINFOHEADER.sizeof);
                imageSize = bmiHeader.biSizeImage;
            }
            byte[] data = new byte[imageSize];
            /* Get the bitmap data */
            if (isDib) {
                OS.MoveMemory(data, bm.bmBits, imageSize);
            } else {
                OS.GetDIBits(hBitmapDC, handle, 0, height, data, bmi, OS.DIB_RGB_COLORS);
            }
            /* Calculate the palette */
            PaletteData palette = null;
            if (depth <= 8) {
                RGB[] rgbs = new RGB[numColors];
                if (isDib) {
                    byte[] colors = new byte[numColors * 4];
                    OS.GetDIBColorTable(hBitmapDC, 0, numColors, colors);
                    int colorIndex = 0;
                    for (int i = 0; i < rgbs.length; i++) {
                        rgbs[i] = new RGB(colors[colorIndex + 2] & 0xFF, colors[colorIndex + 1] & 0xFF,
                                colors[colorIndex] & 0xFF);
                        colorIndex += 4;
                    }
                } else {
                    int srcIndex = BITMAPINFOHEADER.sizeof;
                    for (int i = 0; i < numColors; i++) {
                        rgbs[i] = new RGB(bmi[srcIndex + 2] & 0xFF, bmi[srcIndex + 1] & 0xFF, bmi[srcIndex] & 0xFF);
                        srcIndex += 4;
                    }
                }
                palette = new PaletteData(rgbs);
            } else if (depth == 16) {
                palette = new PaletteData(0x7C00, 0x3E0, 0x1F);
            } else if (depth == 24) {
                palette = new PaletteData(0xFF, 0xFF00, 0xFF0000);
            } else if (depth == 32) {
                palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
            } else {
                SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
            }
            /* Clean up */
            OS.SelectObject(hBitmapDC, hOldBitmap);
            OS.DeleteDC(hBitmapDC);

            /* Release the HDC for the device */
            device.internal_dispose_GC(hDC, null);

            /* Construct and return the ImageData */
            ImageData imageData = new ImageData(width, height, depth, palette, 4, data);
            imageData.transparentPixel = this.transparentPixel;
            imageData.alpha = alpha;
            if (alpha == -1 && alphaData != null) {
                imageData.alphaData = new byte[alphaData.length];
                System.arraycopy(alphaData, 0, imageData.alphaData, 0, alphaData.length);
            }
            return imageData;
        }
        default:
            SWT.error(SWT.ERROR_INVALID_IMAGE);
            return null;
        }
    }

    /**
     * Returns an integer hash code for the receiver. Any two
     * objects that return <code>true</code> when passed to
     * <code>equals</code> must return the same value for this
     * method.
     *
     * @return the receiver's hash
     *
     * @see #equals
     */
    @Override
    public int hashCode() {
        if (imageDataProvider != null) {
            return imageDataProvider.hashCode();
        } else if (imageFileNameProvider != null) {
            return imageFileNameProvider.hashCode();
        } else {
            return (int) handle;
        }
    }

    void init(int width, int height) {
        if (width <= 0 || height <= 0) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        type = SWT.BITMAP;
        long hDC = device.internal_new_GC(null);
        handle = OS.CreateCompatibleBitmap(hDC, width, height);
        /*
        * Feature in Windows.  CreateCompatibleBitmap() may fail
        * for large images.  The fix is to create a DIB section
        * in that case.
        */
        if (handle == 0) {
            int bits = OS.GetDeviceCaps(hDC, OS.BITSPIXEL);
            int planes = OS.GetDeviceCaps(hDC, OS.PLANES);
            int depth = bits * planes;
            if (depth < 16)
                depth = 16;
            handle = createDIB(width, height, depth);
        }
        if (handle != 0) {
            long memDC = OS.CreateCompatibleDC(hDC);
            long hOldBitmap = OS.SelectObject(memDC, handle);
            OS.PatBlt(memDC, 0, 0, width, height, OS.PATCOPY);
            OS.SelectObject(memDC, hOldBitmap);
            OS.DeleteDC(memDC);
        }
        device.internal_dispose_GC(hDC, null);
        if (handle == 0) {
            SWT.error(SWT.ERROR_NO_HANDLES, null, device.getLastError());
        }
    }

    static long createDIB(int width, int height, int depth) {
        BITMAPINFOHEADER bmiHeader = new BITMAPINFOHEADER();
        bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
        bmiHeader.biWidth = width;
        bmiHeader.biHeight = -height;
        bmiHeader.biPlanes = 1;
        bmiHeader.biBitCount = (short) depth;
        bmiHeader.biCompression = OS.BI_RGB;
        byte[] bmi = new byte[BITMAPINFOHEADER.sizeof];
        OS.MoveMemory(bmi, bmiHeader, BITMAPINFOHEADER.sizeof);
        long[] pBits = new long[1];
        return OS.CreateDIBSection(0, bmi, OS.DIB_RGB_COLORS, pBits, 0, 0);
    }

    static long[] init(Device device, Image image, ImageData i) {
        /* Windows does not support 2-bit images. Convert to 4-bit image. */
        if (i.depth == 2) {
            ImageData img = new ImageData(i.width, i.height, 4, i.palette);
            ImageData.blit(ImageData.BLIT_SRC, i.data, i.depth, i.bytesPerLine, i.getByteOrder(), 0, 0, i.width,
                    i.height, null, null, null, ImageData.ALPHA_OPAQUE, null, 0, 0, 0, img.data, img.depth,
                    img.bytesPerLine, i.getByteOrder(), 0, 0, img.width, img.height, null, null, null, false,
                    false);
            img.transparentPixel = i.transparentPixel;
            img.maskPad = i.maskPad;
            img.maskData = i.maskData;
            img.alpha = i.alpha;
            img.alphaData = i.alphaData;
            i = img;
        }
        /*
         * Windows supports 16-bit mask of (0x7C00, 0x3E0, 0x1F),
         * 24-bit mask of (0xFF0000, 0xFF00, 0xFF) and 32-bit mask
         * (0x00FF0000, 0x0000FF00, 0x000000FF) as documented in
         * MSDN BITMAPINFOHEADER.  Make sure the image is
         * Windows-supported.
         */
        if (i.palette.isDirect) {
            final PaletteData palette = i.palette;
            final int redMask = palette.redMask;
            final int greenMask = palette.greenMask;
            final int blueMask = palette.blueMask;
            int newDepth = i.depth;
            int newOrder = ImageData.MSB_FIRST;
            PaletteData newPalette = null;

            switch (i.depth) {
            case 8:
                newDepth = 16;
                newOrder = ImageData.LSB_FIRST;
                newPalette = new PaletteData(0x7C00, 0x3E0, 0x1F);
                break;
            case 16:
                newOrder = ImageData.LSB_FIRST;
                if (!(redMask == 0x7C00 && greenMask == 0x3E0 && blueMask == 0x1F)) {
                    newPalette = new PaletteData(0x7C00, 0x3E0, 0x1F);
                }
                break;
            case 24:
                if (!(redMask == 0xFF && greenMask == 0xFF00 && blueMask == 0xFF0000)) {
                    newPalette = new PaletteData(0xFF, 0xFF00, 0xFF0000);
                }
                break;
            case 32:
                if (!(redMask == 0xFF00 && greenMask == 0xFF0000 && blueMask == 0xFF000000)) {
                    newPalette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
                }
                break;
            default:
                SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
            }
            if (newPalette != null) {
                ImageData img = new ImageData(i.width, i.height, newDepth, newPalette);
                ImageData.blit(ImageData.BLIT_SRC, i.data, i.depth, i.bytesPerLine, i.getByteOrder(), 0, 0, i.width,
                        i.height, redMask, greenMask, blueMask, ImageData.ALPHA_OPAQUE, null, 0, 0, 0, img.data,
                        img.depth, img.bytesPerLine, newOrder, 0, 0, img.width, img.height, newPalette.redMask,
                        newPalette.greenMask, newPalette.blueMask, false, false);
                if (i.transparentPixel != -1) {
                    img.transparentPixel = newPalette.getPixel(palette.getRGB(i.transparentPixel));
                }
                img.maskPad = i.maskPad;
                img.maskData = i.maskData;
                img.alpha = i.alpha;
                img.alphaData = i.alphaData;
                i = img;
            }
        }
        /* Construct bitmap info header by hand */
        RGB[] rgbs = i.palette.getRGBs();
        BITMAPINFOHEADER bmiHeader = new BITMAPINFOHEADER();
        bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
        bmiHeader.biWidth = i.width;
        bmiHeader.biHeight = -i.height;
        bmiHeader.biPlanes = 1;
        bmiHeader.biBitCount = (short) i.depth;
        bmiHeader.biCompression = OS.BI_RGB;
        bmiHeader.biClrUsed = rgbs == null ? 0 : rgbs.length;
        byte[] bmi;
        if (i.palette.isDirect)
            bmi = new byte[BITMAPINFOHEADER.sizeof];
        else
            bmi = new byte[BITMAPINFOHEADER.sizeof + rgbs.length * 4];
        OS.MoveMemory(bmi, bmiHeader, BITMAPINFOHEADER.sizeof);
        /* Set the rgb colors into the bitmap info */
        int offset = BITMAPINFOHEADER.sizeof;
        if (!i.palette.isDirect) {
            for (int j = 0; j < rgbs.length; j++) {
                bmi[offset] = (byte) rgbs[j].blue;
                bmi[offset + 1] = (byte) rgbs[j].green;
                bmi[offset + 2] = (byte) rgbs[j].red;
                bmi[offset + 3] = 0;
                offset += 4;
            }
        }
        long[] pBits = new long[1];
        long hDib = OS.CreateDIBSection(0, bmi, OS.DIB_RGB_COLORS, pBits, 0, 0);
        if (hDib == 0)
            SWT.error(SWT.ERROR_NO_HANDLES);
        /* In case of a scanline pad other than 4, do the work to convert it */
        byte[] data = i.data;
        if (i.scanlinePad != 4 && (i.bytesPerLine % 4 != 0)) {
            data = ImageData.convertPad(data, i.width, i.height, i.depth, i.scanlinePad, 4);
        }
        OS.MoveMemory(pBits[0], data, data.length);

        long[] result = null;
        if (i.getTransparencyType() == SWT.TRANSPARENCY_MASK) {
            /* Get the HDC for the device */
            long hDC = device.internal_new_GC(null);

            /* Create the color bitmap */
            long hdcSrc = OS.CreateCompatibleDC(hDC);
            OS.SelectObject(hdcSrc, hDib);
            long hBitmap = OS.CreateCompatibleBitmap(hDC, i.width, i.height);
            if (hBitmap == 0)
                SWT.error(SWT.ERROR_NO_HANDLES);
            long hdcDest = OS.CreateCompatibleDC(hDC);
            OS.SelectObject(hdcDest, hBitmap);
            OS.BitBlt(hdcDest, 0, 0, i.width, i.height, hdcSrc, 0, 0, OS.SRCCOPY);

            /* Release the HDC for the device */
            device.internal_dispose_GC(hDC, null);

            /* Create the mask. Windows requires icon masks to have a scanline pad of 2. */
            byte[] maskData = ImageData.convertPad(i.maskData, i.width, i.height, 1, i.maskPad, 2);
            long hMask = OS.CreateBitmap(i.width, i.height, 1, 1, maskData);
            if (hMask == 0)
                SWT.error(SWT.ERROR_NO_HANDLES);
            OS.SelectObject(hdcSrc, hMask);
            OS.PatBlt(hdcSrc, 0, 0, i.width, i.height, OS.DSTINVERT);
            OS.DeleteDC(hdcSrc);
            OS.DeleteDC(hdcDest);
            OS.DeleteObject(hDib);

            if (image == null) {
                result = new long[] { hBitmap, hMask };
            } else {
                /* Create the icon */
                ICONINFO info = new ICONINFO();
                info.fIcon = true;
                info.hbmColor = hBitmap;
                info.hbmMask = hMask;
                long hIcon = OS.CreateIconIndirect(info);
                if (hIcon == 0)
                    SWT.error(SWT.ERROR_NO_HANDLES);
                OS.DeleteObject(hBitmap);
                OS.DeleteObject(hMask);
                image.handle = hIcon;
                image.type = SWT.ICON;
            }
        } else {
            if (image == null) {
                result = new long[] { hDib };
            } else {
                image.handle = hDib;
                image.type = SWT.BITMAP;
                image.transparentPixel = i.transparentPixel;
                if (image.transparentPixel == -1) {
                    image.alpha = i.alpha;
                    if (i.alpha == -1 && i.alphaData != null) {
                        int length = i.alphaData.length;
                        image.alphaData = new byte[length];
                        System.arraycopy(i.alphaData, 0, image.alphaData, 0, length);
                    }
                }
            }
        }
        return result;
    }

    static long[] init(Device device, Image image, ImageData source, ImageData mask) {
        /* Create a temporary image and locate the black pixel */
        ImageData imageData;
        int blackIndex = 0;
        if (source.palette.isDirect) {
            imageData = new ImageData(source.width, source.height, source.depth, source.palette);
        } else {
            RGB black = new RGB(0, 0, 0);
            RGB[] rgbs = source.getRGBs();
            if (source.transparentPixel != -1) {
                /*
                 * The source had transparency, so we can use the transparent pixel
                 * for black.
                 */
                RGB[] newRGBs = new RGB[rgbs.length];
                System.arraycopy(rgbs, 0, newRGBs, 0, rgbs.length);
                if (source.transparentPixel >= newRGBs.length) {
                    /* Grow the palette with black */
                    rgbs = new RGB[source.transparentPixel + 1];
                    System.arraycopy(newRGBs, 0, rgbs, 0, newRGBs.length);
                    for (int i = newRGBs.length; i <= source.transparentPixel; i++) {
                        rgbs[i] = new RGB(0, 0, 0);
                    }
                } else {
                    newRGBs[source.transparentPixel] = black;
                    rgbs = newRGBs;
                }
                blackIndex = source.transparentPixel;
                imageData = new ImageData(source.width, source.height, source.depth, new PaletteData(rgbs));
            } else {
                while (blackIndex < rgbs.length) {
                    if (rgbs[blackIndex].equals(black))
                        break;
                    blackIndex++;
                }
                if (blackIndex == rgbs.length) {
                    /*
                     * We didn't find black in the palette, and there is no transparent
                     * pixel we can use.
                     */
                    if ((1 << source.depth) > rgbs.length) {
                        /* We can grow the palette and add black */
                        RGB[] newRGBs = new RGB[rgbs.length + 1];
                        System.arraycopy(rgbs, 0, newRGBs, 0, rgbs.length);
                        newRGBs[rgbs.length] = black;
                        rgbs = newRGBs;
                    } else {
                        /* No room to grow the palette */
                        blackIndex = -1;
                    }
                }
                imageData = new ImageData(source.width, source.height, source.depth, new PaletteData(rgbs));
            }
        }
        if (blackIndex == -1) {
            /* There was no black in the palette, so just copy the data over */
            System.arraycopy(source.data, 0, imageData.data, 0, imageData.data.length);
        } else {
            /* Modify the source image to contain black wherever the mask is 0 */
            int[] imagePixels = new int[imageData.width];
            int[] maskPixels = new int[mask.width];
            for (int y = 0; y < imageData.height; y++) {
                source.getPixels(0, y, imageData.width, imagePixels, 0);
                mask.getPixels(0, y, mask.width, maskPixels, 0);
                for (int i = 0; i < imagePixels.length; i++) {
                    if (maskPixels[i] == 0)
                        imagePixels[i] = blackIndex;
                }
                imageData.setPixels(0, y, source.width, imagePixels, 0);
            }
        }
        imageData.maskPad = mask.scanlinePad;
        imageData.maskData = mask.data;
        return init(device, image, imageData);
    }

    void init(ImageData i) {
        if (i == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        init(device, this, i);
    }

    /**
     * Invokes platform specific functionality to allocate a new GC handle.
     * <p>
     * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
     * API for <code>Image</code>. It is marked public only so that it
     * can be shared within the packages provided by SWT. It is not
     * available on all platforms, and should never be called from
     * application code.
     * </p>
     *
     * @param data the platform specific GC data
     * @return the platform specific GC handle
     *
     * @noreference This method is not intended to be referenced by clients.
     */
    @Override
    public long internal_new_GC(GCData data) {
        if (handle == 0)
            SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
        /*
        * Create a new GC that can draw into the image.
        * Only supported for bitmaps.
        */
        if (type != SWT.BITMAP || memGC != null) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }

        /* Create a compatible HDC for the device */
        long hDC = device.internal_new_GC(null);
        long imageDC = OS.CreateCompatibleDC(hDC);
        device.internal_dispose_GC(hDC, null);
        if (imageDC == 0)
            SWT.error(SWT.ERROR_NO_HANDLES);

        if (data != null) {
            /* Set the GCData fields */
            int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
            if ((data.style & mask) != 0) {
                data.layout = (data.style & SWT.RIGHT_TO_LEFT) != 0 ? OS.LAYOUT_RTL : 0;
            } else {
                data.style |= SWT.LEFT_TO_RIGHT;
            }
            data.device = device;
            data.image = this;
            data.font = device.systemFont;
        }
        return imageDC;
    }

    /**
     * Invokes platform specific functionality to dispose a GC handle.
     * <p>
     * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
     * API for <code>Image</code>. It is marked public only so that it
     * can be shared within the packages provided by SWT. It is not
     * available on all platforms, and should never be called from
     * application code.
     * </p>
     *
     * @param hDC the platform specific GC handle
     * @param data the platform specific GC data
     *
     * @noreference This method is not intended to be referenced by clients.
     */
    @Override
    public void internal_dispose_GC(long hDC, GCData data) {
        OS.DeleteDC(hDC);
    }

    /**
     * Returns <code>true</code> if the image has been disposed,
     * and <code>false</code> otherwise.
     * <p>
     * This method gets the dispose state for the image.
     * When an image has been disposed, it is an error to
     * invoke any other method (except {@link #dispose()}) using the image.
     *
     * @return <code>true</code> when the image is disposed and <code>false</code> otherwise
     */
    @Override
    public boolean isDisposed() {
        return handle == 0;
    }

    /**
     * Sets the color to which to map the transparent pixel.
     * <p>
     * There are certain uses of <code>Images</code> that do not support
     * transparency (for example, setting an image into a button or label).
     * In these cases, it may be desired to simulate transparency by using
     * the background color of the widget to paint the transparent pixels
     * of the image. This method specifies the color that will be used in
     * these cases. For example:</p>
     * <pre>
     *    Button b = new Button();
     *    image.setBackground(b.getBackground());
     *    b.setImage(image);
     * </pre>
     * <p>
     * The image may be modified by this operation (in effect, the
     * transparent regions may be filled with the supplied color).  Hence
     * this operation is not reversible and it is not legal to call
     * this function twice or with a null argument.
     * </p><p>
     * This method has no effect if the receiver does not have a transparent
     * pixel value.
     * </p>
     *
     * @param color the color to use when a transparent pixel is specified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the color is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the color has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
     * </ul>
     */
    public void setBackground(Color color) {
        if (isDisposed())
            SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
        if (color == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (color.isDisposed())
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        if (transparentPixel == -1)
            return;
        transparentColor = -1;

        /* Get the HDC for the device */
        long hDC = device.internal_new_GC(null);

        /* Change the background color in the image */
        BITMAP bm = new BITMAP();
        OS.GetObject(handle, BITMAP.sizeof, bm);
        long hdcMem = OS.CreateCompatibleDC(hDC);
        OS.SelectObject(hdcMem, handle);
        int maxColors = 1 << bm.bmBitsPixel;
        byte[] colors = new byte[maxColors * 4];
        int numColors = OS.GetDIBColorTable(hdcMem, 0, maxColors, colors);
        int offset = transparentPixel * 4;
        colors[offset] = (byte) color.getBlue();
        colors[offset + 1] = (byte) color.getGreen();
        colors[offset + 2] = (byte) color.getRed();
        OS.SetDIBColorTable(hdcMem, 0, numColors, colors);
        OS.DeleteDC(hdcMem);

        /* Release the HDC for the device */
        device.internal_dispose_GC(hDC, null);
    }

    /**
     * Returns a string containing a concise, human-readable
     * description of the receiver.
     *
     * @return a string representation of the receiver
     */
    @Override
    public String toString() {
        if (isDisposed())
            return "Image {*DISPOSED*}";
        return "Image {" + handle + "}";
    }

    /**
     * Invokes platform specific functionality to allocate a new image.
     * <p>
     * <b>IMPORTANT:</b> This method is <em>not</em> part of the public
     * API for <code>Image</code>. It is marked public only so that it
     * can be shared within the packages provided by SWT. It is not
     * available on all platforms, and should never be called from
     * application code.
     * </p>
     *
     * @param device the device on which to allocate the color
     * @param type the type of the image (<code>SWT.BITMAP</code> or <code>SWT.ICON</code>)
     * @param handle the OS handle for the image
     * @return a new image object containing the specified device, type and handle
     *
     * @noreference This method is not intended to be referenced by clients.
     */
    public static Image win32_new(Device device, int type, long handle) {
        Image image = new Image(device);
        image.type = type;
        image.handle = handle;
        return image;
    }

}