/*******************************************************************************
 * Copyright (c) 2003, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.draw2d;

import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.swt.SWT;

/**
 * @author Pratik Shah
 * @since 3.0
 */
public class ImageUtilities {

	/**
	 * Returns a new Image with the given String rotated left (by 90 degrees).
	 * The String will be rendered using the provided colors and fonts. The
	 * client is responsible for disposing the returned Image. Strings cannot
	 * contain newline or tab characters. This method MUST be invoked from the
	 * user-interface (Display) thread.
	 * 
	 * @param string
	 *            the String to be rendered
	 * @param font
	 *            the font
	 * @param foreground
	 *            the text's color
	 * @param background
	 *            the background color
	 * @return an Image which must be disposed
	 */
	public static Image createRotatedImageOfString(String string, Font font,
			Color foreground, Color background) {
		Display display = Display.getCurrent();
		if (display == null)
			SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);

		FontMetrics metrics = FigureUtilities.getFontMetrics(font);
		Dimension strSize = FigureUtilities.getStringExtents(string, font);
	    // UNSUPPORTED - getAscent() not implemented in RAP
//		Image srcImage = new Image(display, strSize.width, metrics.getAscent());
		Image srcImage = new Image(display, strSize.width, metrics.getHeight());
		// UNSUPPORTED - image constructor not implemented in RAP
		// GC gc = new GC(srcImage);
		GC gc = new GC(srcImage.getDevice());
		gc.setFont(font);
		gc.setForeground(foreground);
		gc.setBackground(background);
		gc.fillRectangle(srcImage.getBounds());
		// UNSUPPORTED - getLeading() not implemented in RAP
//		gc.drawString(string, 0, 0 - metrics.getLeading());
		gc.drawString(string, 0, 0);
		Image result = createRotatedImage(srcImage);
		gc.dispose();
		srcImage.dispose();
		return result;
	}

	/**
	 * Returns a new Image that is the given Image rotated left by 90 degrees.
	 * The client is responsible for disposing the returned Image. This method
	 * MUST be invoked from the user-interface (Display) thread.
	 * 
	 * @param srcImage
	 *            the Image that is to be rotated left
	 * @return the rotated Image (the client is responsible for disposing it)
	 */
	public static Image createRotatedImage(Image srcImage) {
		Display display = Display.getCurrent();
		if (display == null)
			SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);

		ImageData srcData = srcImage.getImageData();
		ImageData destData;
		if (srcData.depth < 8)
			destData = rotatePixelByPixel(srcData);
		else
			destData = rotateOptimized(srcData);

		return new Image(display, destData);
	}

	/**
	 * Creates an ImageData representing the given <code>Image</code> shaded
	 * with the given <code>Color</code>.
	 * 
	 * @param fromImage
	 *            Image that has to be shaded
	 * @param shade
	 *            The Color to be used for shading
	 * @return A new ImageData that can be used to create an Image.
	 */
	public static ImageData createShadedImage(Image fromImage, Color shade) {
		org.eclipse.swt.graphics.Rectangle r = fromImage.getBounds();
		ImageData data = fromImage.getImageData();
		PaletteData palette = data.palette;
		if (!palette.isDirect) {
			/* Convert the palette entries */
			RGB[] rgbs = palette.getRGBs();
			for (int i = 0; i < rgbs.length; i++) {
				if (data.transparentPixel != i) {
					RGB color = rgbs[i];
					color.red = determineShading(color.red, shade.getRed());
					color.blue = determineShading(color.blue, shade.getBlue());
					color.green = determineShading(color.green,
							shade.getGreen());
				}
			}
			data.palette = new PaletteData(rgbs);
		} else {
			/* Convert the pixels. */
			int[] scanline = new int[r.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 < r.height; y++) {
				data.getPixels(0, y, r.width, scanline, 0);
				for (int x = 0; x < r.width; x++) {
					int pixel = scanline[x];
					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;
					red = determineShading(red, shade.getRed());
					blue = determineShading(blue, shade.getBlue());
					green = determineShading(green, shade.getGreen());
					red = (redShift < 0) ? red << -redShift : red >> redShift;
					red &= redMask;
					green = (greenShift < 0) ? green << -greenShift
							: green >> greenShift;
					green &= greenMask;
					blue = (blueShift < 0) ? blue << -blueShift
							: blue >> blueShift;
					blue &= blueMask;
					scanline[x] = red | blue | green;
				}
				data.setPixels(0, y, r.width, scanline, 0);
			}
		}
		return data;
	}

	private static int determineShading(int origColor, int shadeColor) {
		return (origColor + shadeColor) / 2;
	}

	private static ImageData rotateOptimized(ImageData srcData) {
		int bytesPerPixel = Math.max(1, srcData.depth / 8);
		int destBytesPerLine = ((srcData.height * bytesPerPixel - 1)
				/ srcData.scanlinePad + 1)
				* srcData.scanlinePad;
		byte[] newData = new byte[destBytesPerLine * srcData.width];
		for (int srcY = 0; srcY < srcData.height; srcY++) {
			for (int srcX = 0; srcX < srcData.width; srcX++) {
				int destX = srcY;
				int destY = srcData.width - srcX - 1;
				int destIndex = (destY * destBytesPerLine)
						+ (destX * bytesPerPixel);
				int srcIndex = (srcY * srcData.bytesPerLine)
						+ (srcX * bytesPerPixel);
				System.arraycopy(srcData.data, srcIndex, newData, destIndex,
						bytesPerPixel);
			}
		}
		return new ImageData(srcData.height, srcData.width, srcData.depth,
				srcData.palette, srcData.scanlinePad, newData);
	}

	private static ImageData rotatePixelByPixel(ImageData srcData) {
		ImageData destData = new ImageData(srcData.height, srcData.width,
				srcData.depth, srcData.palette);
		for (int y = 0; y < srcData.height; y++) {
			for (int x = 0; x < srcData.width; x++) {
				destData.setPixel(y, srcData.width - x - 1,
						srcData.getPixel(x, y));
			}
		}
		return destData;
	}

}
