package com.one;

import com.vmx.ProgressCallback;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Sprite;

/**
 * Класс для обработки изображений.
 */
public class ImageProcessor
{
	protected static final int FP_SHIFT = 16;
	protected static final int FP_MASK = (1 << FP_SHIFT) - 1;

	protected static Hashtable srcimages = new Hashtable();

	protected static int[] SQUARES = new int[256];

	static
	{
		for(int i = 0; i < 256; i++)
		{
			SQUARES[i] = i * i;
		}
	}

	/**
	 * Функция масштабирования изображений.
	 *
	 * Если один из конечных размеров меньше 0,
	 * он вычисляется на основе другого с сохранением пропорций.
	 *
	 * Если оба конечных размера меньше 0,
	 * или они равны размеру исходной картинки,
	 * возвращается исходная картинка.
	 *
	 * При масштабировании может использоваться бикубическая интерполяция
	 * для получения более качественных изображений (особенно при увеличении).
	 * 
	 * Если для бикубической интерполяции не хватит памяти,
	 * будет использоваться билинейная интерполяция.
	 *
	 * Если для масштабирования всей картинки не хватит памяти,
	 * она будет масштабироваться по частям,
	 * при этом альфа-канал не сохраняется.
	 *
	 * @param mask исходная картинка
	 * @param destw конечная ширина
	 * @param desth конечная высота
	 * @param interpolate true, если нужно использовать интерполяцию
	 * @param alpha true, если нужно сохранить альфа-канал
	 * @return масштабированная картинка
	 */
	public static Image scaleImage(Image source, int destw, int desth, boolean interpolate, boolean alpha)
	{
		if(source == null)
		{
			return null;
		}
		
		try
		{
			int srcw = source.getWidth();
			int srch = source.getHeight();

			if(srcw == destw && srch == desth)
			{
				return source;
			}

			if(destw < 0)
			{
				if(desth < 0)
				{
					return source;
				}
				else
				{
					destw = srcw * desth / srch;
				}
			}
			else if(desth < 0)
			{
				desth = srch * destw / srcw;
			}
			
			int[] src = null;
			int[] dst = null;
			int fragcount = 1;
			int srcfw, srcfh;
			int dstfw, dstfh;
			
			do
			{
				srcfw = srcw / fragcount;
				srcfh = srch / fragcount;
				dstfw = destw / fragcount;
				dstfh = desth / fragcount;
				
				try
				{
					src = new int[srcfw * srcfh];
					dst = new int[dstfw * dstfh];
				}
				catch(OutOfMemoryError oome)
				{
					src = null;
					dst = null;
					fragcount++;
				}
			}
			while(src == null || dst == null);

			if(fragcount == 1)
			{
				source.getRGB(src, 0, srcw, 0, 0, srcw, srch);

				if(interpolate)
				{
					bicubicScaleRGB(src, dst, srcw, srch, destw, desth, alpha);
				}
				else
				{
					scaleRGB(src, dst, srcw, srch, destw, desth, alpha);
				}

				return Image.createRGBImage(dst, destw, desth, alpha);
			}
			else
			{
				Image scaled = Image.createImage(destw, desth);
				Graphics g = scaled.getGraphics();

				int fx, fy;

				for(fx = 0; fx < fragcount; fx++)
				{
					for(fy = 0; fy < fragcount; fy++)
					{
						source.getRGB(src, 0, srcfw, fx * srcfw, fy * srcfh, srcfw, srcfh);

						if(interpolate && (dstfw > srcfw || dstfh > srcfh))
						{
							bicubicScaleRGB(src, dst, srcfw, srcfh, dstfw, dstfh, false);
						}
						else
						{
							scaleRGB(src, dst, srcfw, srcfh, dstfw, dstfh, false);
						}

						g.drawRGB(dst, 0, dstfw, fx * dstfw, fy * dstfh, dstfw, dstfh, false);
					}
				}

				return scaled;
			}
		}
		catch(Throwable t)
		{
			return source;
		}
	}

	/**
	 * Составить указанное изображение из цветов,
	 * содержащихся в указанной палитре.
	 *
	 * Максимальное значение в callback увеличивается автоматически.
	 *
	 * @param image исходная картинка
	 * @param quantizer откуда брать палитру
	 * @param dither использовать ли распределение ошибки
	 * @param callback для наблюдения за прогрессом
	 * @return новая картинка
	 */
	public static Image quantizeImage(Image image, ColorQuantizer quantizer, boolean dither, ProgressCallback callback)
	{
		try
		{
			int width = image.getWidth();
			int height = image.getHeight();

			int[] src = null;
			int[] dst = null;
			int fragcount = 1;
			int fw, fh;

			do
			{
				fw = width / fragcount;
				fh = height / fragcount;

				try
				{
					src = new int[fw * fh];
					dst = new int[fw * fh];
				}
				catch(OutOfMemoryError oome)
				{
					src = null;
					dst = null;
					fragcount++;
				}
			}
			while(src == null || dst == null);

			if(callback != null)
			{
				callback.setProgress(0);
				callback.setMax(height * fragcount);
			}

			if(fragcount == 1)
			{
				image.getRGB(src, 0, width, 0, 0, width, height);

				if(dither)
				{
					ditherRGB(src, dst, width, height, quantizer, callback);
				}
				else
				{
					quantizeRGB(src, dst, width, height, quantizer, callback);
				}

				return Image.createRGBImage(dst, width, height, true);
			}
			else
			{
				Image res = Image.createImage(width, height);
				Graphics g = res.getGraphics();

				int fx, fy;

				for(fx = 0; fx < fragcount; fx++)
				{
					for(fy = 0; fy < fragcount; fy++)
					{
						image.getRGB(src, 0, fw, fx * fw, fy * fh, fw, fh);

						if(dither)
						{
							ditherRGB(src, dst, fw, fh, quantizer, callback);
						}
						else
						{
							quantizeRGB(src, dst, fw, fh, quantizer, callback);
						}

						g.drawRGB(dst, 0, fw, fx * fw, fy * fh, fw, fh, false);
					}
				}

				return res;
			}
		}
		catch(Throwable t)
		{
			return image;
		}
	}

	/**
	 * Яркость одного изображения применяется
	 * как альфа-канал другого изображения.
	 *
	 * В целях оптимизации скорости процесса вычисление яркости
	 * реализовано с использованием только логических операций
	 * и сдвигов, поэтому яркость вычисляется как
	 * (R + 2G + B) / 4 вместо (R + G + B) / 3.
	 *
	 * @param image исходное изображение, там же получается результат
	 * @param mask изображение, применяемое в качестве маски
	 */
	public static void lightAlphaMask(int[] image, int[] mask)
	{
		for(int i = 0; i < image.length; i++)
		{
			image[i] = (image[i] & 0xFFFFFF) | (((((mask[i] >> 16) & 0xFF) + ((mask[i] >> 7) & 0x1FE) + (mask[i] & 0xFF)) << 22) & 0xFF000000);
		}
	}

	/**
	 * Альфа-композиция двух изображений.
	 *
	 * @param back задний план, там же получается результат
	 * @param fore передний план
	 */
	public static void blendImages(int[] back, int[] fore)
	{
		int aa, ar, ag, ab;
		int ba, br, bg, bb;
		int ca, cr, cg, cb;

		for(int i = 0; i < fore.length; i++)
		{
			aa = (fore[i] >> 24) & 0xFF;
			ar = (fore[i] >> 16) & 0xFF;
			ag = (fore[i] >> 8) & 0xFF;
			ab = fore[i] & 0xFF;

			ba = (back[i] >> 24) & 0xFF;
			br = (back[i] >> 16) & 0xFF;
			bg = (back[i] >> 8) & 0xFF;
			bb = back[i] & 0xFF;

			ca = aa + ba * (255 - aa) / 255;

			if(ca > 0)
			{
				cr = (ar * aa + br * ba * (255 - aa) / 255) / ca;
				cg = (ag * aa + bg * ba * (255 - aa) / 255) / ca;
				cb = (ab * aa + bb * ba * (255 - aa) / 255) / ca;
			}
			else
			{
				cr = ar;
				cg = ag;
				cb = ab;
			}

			back[i] = (ca << 24) | (cr << 16) | (cg << 8) | cb;
		}
	}

	/**
	 * Достать картинку, заданную базовой папкой и
	 * спецификатором вида "file.ext @ x, y, w, h".
	 * Однако, спецификатор, состоящий только из имени картинки, тоже работает.
	 *
	 * Исходные изображения, чтобы каждый раз не создавать заново,
	 * кешируются в Hashtable и достаются потом оттуда.
	 * Поэтому после загрузки всех необходимых картинок рекомендуется
	 * для освобождения памяти вызвать releaseCachedImages().
	 *
	 * @param base базовая папка, в которой искать картинки
	 * @param specifier спецификатор картинки (см. выше)
	 * @return получившаяся картинка
	 * @throws IOException если не удалось достать исходную картинку
	 */
	public static Image getImage(String base, String specifier) throws IOException
	{
		int index = specifier.indexOf('@');

		if(index >= 0)
		{
			String filename = specifier.substring(0, index).trim();

			if(!filename.startsWith("/"))
			{
				if(!base.startsWith("/"))
				{
					base = "/" + base;
				}

				filename = base + filename;
			}
			
			Image source = (Image)srcimages.get(filename);

			if(source == null)
			{
				source = Image.createImage(filename);
				srcimages.put(filename, source);
			}

			String[] position = TextProcessor.vectorToStringArray(StringPattern.tokenizeString(specifier.substring(index + 1), ','));

			if(position.length >= 5)
			{
				return Image.createImage(source, Integer.parseInt(position[0].trim()), Integer.parseInt(position[1].trim()),
						                         Integer.parseInt(position[2].trim()), Integer.parseInt(position[3].trim()),
												 Integer.parseInt(position[4].trim()));
			}
			else
			{
				return Image.createImage(source, Integer.parseInt(position[0].trim()), Integer.parseInt(position[1].trim()),
						                         Integer.parseInt(position[2].trim()), Integer.parseInt(position[3].trim()),
												 Sprite.TRANS_NONE);
			}
		}
		else
		{
			String filename = specifier.trim();

			if(!filename.startsWith("/"))
			{
				if(!base.startsWith("/"))
				{
					base = "/" + base;
				}

				filename = base + filename;
			}

			Image source = (Image)srcimages.get(filename);

			if(source == null)
			{
				source = Image.createImage(filename);
				srcimages.put(filename, source);
			}

			return source;
		}
	}

	/**
	 * Добавить картинку в Hashtable для getImage().
	 *
	 * @param name имя картинки, например "/img/icons.png"
	 * @param image собственно картинка
	 */
	public static void addImageToCache(String name, Image image)
	{
		srcimages.put(name, image);
	}

	/**
	 * Очистить Hashtable с картинками для getImage().
	 */
	public static void releaseCachedImages()
	{
		srcimages.clear();
	}

	/**
	 * Записать Image в DataOutputStream.
	 *
	 * Несжатая картинка хранится в потоке в следующем виде:
	 * int width, int height, int[width * height] pixels
	 * 
	 * Сжатая картинка хранится в потоке в следующем виде:
	 * int length, byte[length] data
	 *
	 * @param dos поток, куда писать картинку
	 * @param image записываемая картинка
	 * @param mode режим цвета для PNG, или -1 для несжатой картинки
	 * @param callback для наблюдения за прогрессом
	 * @throws IOException
	 */
	public static void writeImage(DataOutputStream dos, Image image, int mode, int filter, ProgressCallback callback) throws IOException
	{
		if(image != null)
		{
			if(mode >= 0)
			{
				try
				{
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					DataOutputStream out = new DataOutputStream(baos);

					PNGFile.writePNG(image, out, mode, filter, callback);

					byte[] data = baos.toByteArray();
					out.close();

					dos.writeBoolean(true);
					dos.writeInt(data.length);
					dos.write(data);
					
					return;
				}
				catch(OutOfMemoryError oome)
				{
				}
			}

			int[] rgb = new int[image.getWidth() * image.getHeight()];
			image.getRGB(rgb, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight());

			dos.writeBoolean(false);

			dos.writeInt(image.getWidth());
			dos.writeInt(image.getHeight());

			for(int i = 0; i < rgb.length; i++)
			{
				dos.writeInt(rgb[i]);
			}
		}
		else
		{
			dos.writeBoolean(false);

			dos.writeInt(-1);
			dos.writeInt(-1);
		}
	}

	/**
	 * Считать Image из DataInputStream.
	 *
	 * Несжатая картинка хранится в потоке в следующем виде:
	 * int width, int height, int[width * height] pixels
	 * 
	 * Сжатая картинка хранится в потоке в следующем виде:
	 * int length, byte[length] data
	 *
	 * @param dis поток, из которого читать картинку
	 * @return считанная картинка
	 * @throws IOException
	 */
	public static Image readImage(DataInputStream dis) throws IOException
	{
		boolean encoded = dis.readBoolean();

		if(encoded)
		{
			int length = dis.readInt();
			byte[] data = new byte[length];

			dis.readFully(data);

			return Image.createImage(data, 0, length);
		}
		else
		{
			int width = dis.readInt();
			int height = dis.readInt();

			if(width >= 0 && height >= 0)
			{
				int[] rgb = new int[width * height];

				for(int i = 0; i < rgb.length; i++)
				{
					rgb[i] = dis.readInt();
				}

				return Image.createRGBImage(rgb, width, height, true);
			}
			else
			{
				return null;
			}
		}
	}
	
	/**
	 * Упаковать набор отдельных значений A, R, G, B
	 * в единое число, содержащее цвет в формате ARGB.
	 *
	 * Все значения в процессе приводятся к диапазону 0 ~ 255.
	 *
	 * @param a значение альфа-канала
	 * @param r значение красного канала
	 * @param g значение зеленого канала
	 * @param b значение синего канала
	 * @return цвет в формате ARGB
	 */
	public static int getColor(int a, int r, int g, int b)
	{
		if(a < 0)
		{
			a = 0;
		}
		else if(a > 255)
		{
			a = 255;
		}

		if(r < 0)
		{
			r = 0;
		}
		else if(r > 255)
		{
			r = 255;
		}

		if(g < 0)
		{
			g = 0;
		}
		else if(g > 255)
		{
			g = 255;
		}

		if(b < 0)
		{
			b = 0;
		}
		else if(b > 255)
		{
			b = 255;
		}

		return (a << 24) | (r << 16) | (g << 8) | b;
	}

	/**
	 * Получить "расстояние" между двумя цветами.
	 *
	 * Расстояние вычисляется как длина четырехмерного вектора
	 * с координатами A, R, G и B.
	 *
	 * @param color1 первый цвет
	 * @param color2 второй цвет
	 * @return расстояние между цветами
	 */
	public static int colorDistance(int color1, int color2)
	{
		return SQUARES[Math.abs(((color1 >> 24) & 0xFF) - ((color2 >> 24) & 0xFF))] +
			   SQUARES[Math.abs(((color1 >> 16) & 0xFF) - ((color2 >> 16) & 0xFF))] +
			   SQUARES[Math.abs(((color1 >> 8) & 0xFF) - ((color2 >> 8) & 0xFF))] +
			   SQUARES[Math.abs((color1 & 0xFF) - (color2 & 0xFF))];
	}

	/**
	 * Найти индекс цвета в палитре,
	 * который ближе всего к данному цвету.
	 *
	 * @param color цвет, для которого ищется соответствие
	 * @param palette палитра, из которой выбираются цвета
	 * @return индекс цвета в палитре
	 */
	public static int nearestColor(int color, int[] palette)
	{
		int mindistance = colorDistance(0, -1) + 1;
		int distance, index = -1;

		for(int i = 0; i < palette.length; i++)
		{
			distance = colorDistance(palette[i], color);

			if(distance == 0)
			{
				return i;
			}
			else if(distance < mindistance)
			{
				index = i;
				mindistance = distance;
			}
		}

		return index;
	}

	/**
	 * Масштабирование массива RGB / ARGB точек.
	 * Используется метод усреднения / ближайшей точки.
	 *
	 * @param srcPixels исходный массив
	 * @param destPixels конечный массив
	 * @param srcw исходная ширина
	 * @param srch исходная высота
	 * @param destw конечная ширина
	 * @param desth конечная высота
	 * @param alpha массив ARGB
	 */
	public static void scaleRGB(int[] srcPixels, int[] destPixels, int srcw, int srch, int destw, int desth, boolean alpha)
	{
		int wscan, hscan, scancount;
		int x, y, sx, sy, cx, cy;
		int a, r, g, b;
		int idx;
		int rx, ry;

		rx = (srcw << FP_SHIFT) / destw;
		ry = (srch << FP_SHIFT) / desth;

		wscan = rx >>> FP_SHIFT;
		hscan = ry >>> FP_SHIFT;

		if(wscan == 0)
		{
			wscan = 1;
		}

		if(hscan == 0)
		{
			hscan = 1;
		}

		scancount = wscan * hscan;

		try
		{
			if(alpha)
			{
				for(x = 0; x < destw; x++)
				{
					for(y = 0; y < desth; y++)
					{
						sx = (x * rx) >>> FP_SHIFT ;
						sy = (y * ry) >>> FP_SHIFT;

						a = r = g = b = 0;

						for(cx = 0; cx < wscan; cx++)
						{
							for(cy = 0; cy < hscan; cy++)
							{
								idx = sx + cx + (sy + cy) * srcw;

								a += (srcPixels[idx] >> 24) & 0xFF;
								r += (srcPixels[idx] >> 16) & 0xFF;
								g += (srcPixels[idx] >> 8) & 0xFF;
								b += (srcPixels[idx]) & 0xFF;
							}
						}

						a /= scancount;
						r /= scancount;
						g /= scancount;
						b /= scancount;

						destPixels[x + y * destw] = (a << 24) | (r << 16) | (g << 8) | b;
					}
				}
			}
			else
			{
				for(x = 0; x < destw; x++)
				{
					for(y = 0; y < desth; y++)
					{
						sx = (x * rx) >>> FP_SHIFT ;
						sy = (y * ry) >>> FP_SHIFT;

						r = g = b = 0;

						for(cx = 0; cx < wscan; cx++)
						{
							for(cy = 0; cy < hscan; cy++)
							{
								idx = sx + cx + (sy + cy) * srcw;

								r += (srcPixels[idx] >> 16) & 0xFF;
								g += (srcPixels[idx] >> 8) & 0xFF;
								b += (srcPixels[idx]) & 0xFF;
							}
						}

						r /= scancount;
						g /= scancount;
						b /= scancount;

						destPixels[x + y * destw] = (r << 16) | (g << 8) | b;
					}
				}
			}
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
		}
	}

	/**
	 * Масштабирование массива RGB / ARGB точек.
	 * Используется метод билинейной интерполяции.
	 *
	 * @param srcPixels исходный массив
	 * @param destPixels конечный массив
	 * @param srcw исходная ширина
	 * @param srch исходная высота
	 * @param destw конечная ширина
	 * @param desth конечная высота
	 * @param alpha массив ARGB
	 */
	public static void bilinearScaleRGB(int[] srcPixels, int[] destPixels, int srcw, int srch, int destw, int desth, boolean alpha)
	{
		int x, y, rx, ry, ix, iy, fx, fy;
		int idx1, idx2, idx3, idx4;
		int a, r, g, b;

		try
		{
			if(alpha)
			{
				for(x = 0; x < destw; x++)
				{
					for(y = 0; y < desth; y++)
					{
						rx = (x << FP_SHIFT) / destw * srcw;
						ry = (y << FP_SHIFT) / desth * srch;

						ix = rx >>> FP_SHIFT;
						iy = ry >>> FP_SHIFT;

						fx = rx & FP_MASK;
						fy = ry & FP_MASK;

						idx1 = idx2 = idx3 = idx4 = ix + iy * srcw;

						if(ix < srcw - 1)
						{
							idx2++;
							idx4++;
						}

						if(iy < srch - 1)
						{
							idx3 += srcw;
							idx4 += srcw;
						}

						a = (((((srcPixels[idx1] >> 24) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx2] >> 24) & 0xFF) * fx) >>> FP_SHIFT) * (FP_MASK - fy) + ((((srcPixels[idx3] >> 24) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx4] >> 24) & 0xFF) * fx) >>> FP_SHIFT) * fy) >>> FP_SHIFT;
						r = (((((srcPixels[idx1] >> 16) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx2] >> 16) & 0xFF) * fx) >>> FP_SHIFT) * (FP_MASK - fy) + ((((srcPixels[idx3] >> 16) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx4] >> 16) & 0xFF) * fx) >>> FP_SHIFT) * fy) >>> FP_SHIFT;
						g = (((((srcPixels[idx1] >> 8) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx2] >> 8) & 0xFF) * fx) >>> FP_SHIFT) * (FP_MASK - fy) + ((((srcPixels[idx3] >> 8) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx4] >> 8) & 0xFF) * fx) >>> FP_SHIFT) * fy) >>> FP_SHIFT;
						b = ((((srcPixels[idx1] & 0xFF) * (FP_MASK - fx) + (srcPixels[idx2] & 0xFF) * fx) >>> FP_SHIFT) * (FP_MASK - fy) + (((srcPixels[idx3] & 0xFF) * (FP_MASK - fx) + (srcPixels[idx4] & 0xFF) * fx) >>> FP_SHIFT) * fy) >>> FP_SHIFT;

						destPixels[x + y * destw] = (a << 24) | (r << 16) | (g << 8) | b;
					}
				}
			}
			else
			{
				for(x = 0; x < destw; x++)
				{
					for(y = 0; y < desth; y++)
					{
						rx = (x << FP_SHIFT) / destw * srcw;
						ry = (y << FP_SHIFT) / desth * srch;

						ix = rx >>> FP_SHIFT;
						iy = ry >>> FP_SHIFT;

						fx = rx & FP_MASK;
						fy = ry & FP_MASK;

						idx1 = idx2 = idx3 = idx4 = ix + iy * srcw;

						if(ix < srcw - 1)
						{
							idx2++;
							idx4++;
						}

						if(iy < srch - 1)
						{
							idx3 += srcw;
							idx4 += srcw;
						}

						r = (((((srcPixels[idx1] >> 16) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx2] >> 16) & 0xFF) * fx) >>> FP_SHIFT) * (FP_MASK - fy) + ((((srcPixels[idx3] >> 16) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx4] >> 16) & 0xFF) * fx) >>> FP_SHIFT) * fy) >>> FP_SHIFT;
						g = (((((srcPixels[idx1] >> 8) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx2] >> 8) & 0xFF) * fx) >>> FP_SHIFT) * (FP_MASK - fy) + ((((srcPixels[idx3] >> 8) & 0xFF) * (FP_MASK - fx) + ((srcPixels[idx4] >> 8) & 0xFF) * fx) >>> FP_SHIFT) * fy) >>> FP_SHIFT;
						b = ((((srcPixels[idx1] & 0xFF) * (FP_MASK - fx) + (srcPixels[idx2] & 0xFF) * fx) >>> FP_SHIFT) * (FP_MASK - fy) + (((srcPixels[idx3] & 0xFF) * (FP_MASK - fx) + (srcPixels[idx4] & 0xFF) * fx) >>> FP_SHIFT) * fy) >>> FP_SHIFT;

						destPixels[x + y * destw] = (r << 16) | (g << 8) | b;
					}
				}
			}
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
		}
	}

	/**
	 * Масштабирование массива RGB / ARGB точек.
	 * Используется метод бикубической интерполяции.
	 *
	 * @param srcPixels исходный массив
	 * @param destPixels конечный массив
	 * @param srcw исходная ширина
	 * @param srch исходная высота
	 * @param destw конечная ширина
	 * @param desth конечная высота
	 * @param alpha массив ARGB
	 */
	public static void bicubicScaleRGB(int[] srcPixels, int[] destPixels, int srcw, int srch, int destw, int desth, boolean alpha)
	{
		try
		{
			float[][] srca = alpha ? new float[srch][srcw] : null;

			float[][] srcr = new float[srch][srcw];
			float[][] srcg = new float[srch][srcw];
			float[][] srcb = new float[srch][srcw];

			int x, y, index;

			if(alpha)
			{
				for(y = 0; y < srch; y++)
				{
					for(x = 0; x < srcw; x++)
					{
						index = x + y * srcw;

						srca[y][x] = (float)((srcPixels[index] >> 24) & 0xFF);
						srcr[y][x] = (float)((srcPixels[index] >> 16) & 0xFF);
						srcg[y][x] = (float)((srcPixels[index] >> 8) & 0xFF);
						srcb[y][x] = (float)(srcPixels[index] & 0xFF);
					}
				}
			}
			else
			{
				for(y = 0; y < srch; y++)
				{
					for(x = 0; x < srcw; x++)
					{
						index = x + y * srcw;

						srcr[y][x] = (float)((srcPixels[index] >> 16) & 0xFF);
						srcg[y][x] = (float)((srcPixels[index] >> 8) & 0xFF);
						srcb[y][x] = (float)(srcPixels[index] & 0xFF);
					}
				}
			}

			float[][] desta = alpha ? CubicSpline.bicubicResampleCartesian(srca, srch, srcw, desth, destw) : null;

			float[][] destr = CubicSpline.bicubicResampleCartesian(srcr, srch, srcw, desth, destw);
			float[][] destg = CubicSpline.bicubicResampleCartesian(srcg, srch, srcw, desth, destw);
			float[][] destb = CubicSpline.bicubicResampleCartesian(srcb, srch, srcw, desth, destw);

			if(alpha)
			{
				for(y = 0; y < desth; y++)
				{
					for(x = 0; x < destw; x++)
					{
						destPixels[x + y * destw] = getColor((int)desta[y][x], (int)destr[y][x], (int)destg[y][x], (int)destb[y][x]);
					}
				}
			}
			else
			{
				for(y = 0; y < desth; y++)
				{
					for(x = 0; x < destw; x++)
					{
						destPixels[x + y * destw] = getColor(255, (int)destr[y][x], (int)destg[y][x], (int)destb[y][x]);
					}
				}
			}
		}
		catch(OutOfMemoryError oome)
		{
			bilinearScaleRGB(srcPixels, destPixels, srcw, srch, destw, desth, alpha);
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
		}
	}

	/**
	 * Заменить цвета точек в массиве ARGB на цвета из палитры.
	 * Алгоритм распределения ошибки не применяется.
	 *
	 * @param src исходный массив
	 * @param image конечный массив
	 * @param width ширина
	 * @param height высота
	 * @param quantizer откуда брать палитру
	 * @param callback для наблюдения за прогрессом
	 */
	public static void quantizeRGB(int[] src, int[] dest, int width, int height, ColorQuantizer quantizer, ProgressCallback callback)
	{
		int[] palette = quantizer.getPalette();

		try
		{
			int index;

			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					index = x + y * width;
					dest[index] = palette[quantizer.getColorIndex(src[index])];
				}

				if(callback != null)
				{
					callback.progress(1);
				}
			}
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
		}
	}

	/**
	 * Заменить цвета точек в массиве ARGB на цвета из палитры.
	 * Применяется алгоритм распределения ошибки Флойда-Штейнберга.
	 *
	 * @param src исходный массив
	 * @param image конечный массив
	 * @param width ширина
	 * @param height высота
	 * @param quantizer откуда брать палитру
	 * @param callback для наблюдения за прогрессом
	 */
	public static void ditherRGB(int[] src, int[] dest, int width, int height, ColorQuantizer quantizer, ProgressCallback callback)
	{
		int[] palette = quantizer.getPalette();
		
		try
		{
			int da, dr, dg, db;
			int ca, cr, cg, cb;

			int mx = width - 1;
			int my = height - 1;
			int index, localindex;

			System.arraycopy(src, 0, dest, 0, src.length);

			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					index = x + y * width;

					ca = (dest[index] >> 24) & 0xFF;
					cr = (dest[index] >> 16) & 0xFF;
					cg = (dest[index] >> 8) & 0xFF;
					cb = dest[index] & 0xFF;

					dest[index] = palette[quantizer.getColorIndex(dest[index])];

					da = ca - ((dest[index] >> 24) & 0xFF);
					dr = cr - ((dest[index] >> 16) & 0xFF);
					dg = cg - ((dest[index] >> 8) & 0xFF);
					db = cb - (dest[index] & 0xFF);

					if(x < mx)
					{
						localindex = index + 1;

						ca = ((dest[localindex] >> 24) & 0xFF) + ((da * 7) >> 4);
						cr = ((dest[localindex] >> 16) & 0xFF) + ((dr * 7) >> 4);
						cg = ((dest[localindex] >> 8) & 0xFF) + ((dg * 7) >> 4);
						cb = (dest[localindex] & 0xFF) + ((db * 7) >> 4);

						dest[localindex] = getColor(ca, cr, cg, cb);
					}

					if(x < my)
					{
						if(x > 0)
						{
							localindex = index + width - 1;

							ca = ((dest[localindex] >> 24) & 0xFF) + ((da * 3) >> 4);
							cr = ((dest[localindex] >> 16) & 0xFF) + ((dr * 3) >> 4);
							cg = ((dest[localindex] >> 8) & 0xFF) + ((dg * 3) >> 4);
							cb = (dest[localindex] & 0xFF) + ((db * 3) >> 4);

							dest[localindex] = getColor(ca, cr, cg, cb);
						}

						localindex = index + width;

						ca = ((dest[localindex] >> 24) & 0xFF) + ((da * 5) >> 4);
						cr = ((dest[localindex] >> 16) & 0xFF) + ((dr * 5) >> 4);
						cg = ((dest[localindex] >> 8) & 0xFF) + ((dg * 5) >> 4);
						cb = (dest[localindex] & 0xFF) + ((db * 5) >> 4);

						dest[localindex] = getColor(ca, cr, cg, cb);

						if(x < mx)
						{
							localindex = index + width + 1;

							ca = ((dest[localindex] >> 24) & 0xFF) + (da >> 4);
							cr = ((dest[localindex] >> 16) & 0xFF) + (dr >> 4);
							cg = ((dest[localindex] >> 8) & 0xFF) + (dg >> 4);
							cb = (dest[localindex] & 0xFF) + (db >> 4);

							dest[localindex] = getColor(ca, cr, cg, cb);
						}
					}
				}

				if(callback != null)
				{
					callback.progress(1);
				}
			}
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
		}
	}
}