using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Harlinn.WindowsPhone.XNA.Engine.Extensions;
namespace Harlinn.WindowsPhone.XNA.Engine.Imaging
{
public partial class XNAImage
{
/// <summary>
/// Creates a new cropped XNAImage.
/// </summary>
/// <param name="x">The x coordinate of the rectangle that defines the crop region.</param>
/// <param name="y">The y coordinate of the rectangle that defines the crop region.</param>
/// <param name="width">The width of the rectangle that defines the crop region.</param>
/// <param name="height">The height of the rectangle that defines the crop region.</param>
/// <returns>A new XNAImage that is a cropped version of the input.</returns>
public XNAImage Crop(int x, int y, int width, int height)
{
var srcWidth = this.width;
var srcHeight = this.height;
// If the rectangle is completly out of the bitmap
if (x > srcWidth || y > srcHeight)
{
return new XNAImage();
}
// Clamp to boundaries
if (x < 0) x = 0;
if (x + width > srcWidth) width = srcWidth - x;
if (y < 0) y = 0;
if (y + height > srcHeight) height = srcHeight - y;
// Copy the pixels line by line using fast BlockCopy
var result = new XNAImage(width, height);
for (var line = 0; line < height; line++)
{
var srcOff = ((y + line) * srcWidth + x) * 4;
var dstOff = line * width * 4;
Buffer.BlockCopy(pixels, srcOff, result.Pixels, dstOff, width * 4);
}
return result;
}
/// <summary>
/// Creates a new cropped XNAImage.
/// </summary>
/// <param name="region">The rectangle that defines the crop region.</param>
/// <returns>A new XNAImage that is a cropped version of the input.</returns>
public XNAImage Crop(Rectangle region)
{
return Crop((int)region.X, (int)region.Y, (int)region.Width, (int)region.Height);
}
/// <summary>
/// Creates a new resized XNAImage.
/// </summary>
/// <param name="width">The new desired width.</param>
/// <param name="height">The new desired height.</param>
/// <param name="interpolation">The interpolation method that should be used.</param>
/// <returns>A new XNAImage that is a resized version of the input.</returns>
public XNAImage Resize(int width, int height, Interpolation interpolation)
{
int[] pd = Resize(pixels, this.width, this.height, width, height, interpolation);
XNAImage result = new XNAImage(width, height,pd);
return result;
}
/// <summary>
/// Creates a new resized bitmap.
/// </summary>
/// <param name="pixels">The source pixels.</param>
/// <param name="widthSource">The width of the source pixels.</param>
/// <param name="heightSource">The height of the source pixels.</param>
/// <param name="width">The new desired width.</param>
/// <param name="height">The new desired height.</param>
/// <param name="interpolation">The interpolation method that should be used.</param>
/// <returns>A new bitmap that is a resized version of the input.</returns>
public static int[] Resize(int[] pixels, int widthSource, int heightSource, int width, int height, Interpolation interpolation)
{
var pd = new int[width * height];
var xs = (float)widthSource / width;
var ys = (float)heightSource / height;
float fracx, fracy, ifracx, ifracy, sx, sy, l0, l1, rf, gf, bf;
int c, x0, x1, y0, y1;
byte c1a, c1r, c1g, c1b, c2a, c2r, c2g, c2b, c3a, c3r, c3g, c3b, c4a, c4r, c4g, c4b;
byte a, r, g, b;
// Nearest Neighbor
if (interpolation == Interpolation.NearestNeighbor)
{
var srcIdx = 0;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
sx = x * xs;
sy = y * ys;
x0 = (int)sx;
y0 = (int)sy;
pd[srcIdx++] = pixels[y0 * widthSource + x0];
}
}
}
// Bilinear
else if (interpolation == Interpolation.Bilinear)
{
var srcIdx = 0;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
sx = x * xs;
sy = y * ys;
x0 = (int)sx;
y0 = (int)sy;
// Calculate coordinates of the 4 interpolation points
fracx = sx - x0;
fracy = sy - y0;
ifracx = 1f - fracx;
ifracy = 1f - fracy;
x1 = x0 + 1;
if (x1 >= widthSource)
{
x1 = x0;
}
y1 = y0 + 1;
if (y1 >= heightSource)
{
y1 = y0;
}
// Read source color
c = pixels[y0 * widthSource + x0];
c1a = (byte)(c >> 24);
c1r = (byte)(c >> 16);
c1g = (byte)(c >> 8);
c1b = (byte)(c);
c = pixels[y0 * widthSource + x1];
c2a = (byte)(c >> 24);
c2r = (byte)(c >> 16);
c2g = (byte)(c >> 8);
c2b = (byte)(c);
c = pixels[y1 * widthSource + x0];
c3a = (byte)(c >> 24);
c3r = (byte)(c >> 16);
c3g = (byte)(c >> 8);
c3b = (byte)(c);
c = pixels[y1 * widthSource + x1];
c4a = (byte)(c >> 24);
c4r = (byte)(c >> 16);
c4g = (byte)(c >> 8);
c4b = (byte)(c);
// Calculate colors
// Alpha
l0 = ifracx * c1a + fracx * c2a;
l1 = ifracx * c3a + fracx * c4a;
a = (byte)(ifracy * l0 + fracy * l1);
// Red
l0 = ifracx * c1r * c1a + fracx * c2r * c2a;
l1 = ifracx * c3r * c3a + fracx * c4r * c4a;
rf = ifracy * l0 + fracy * l1;
// Green
l0 = ifracx * c1g * c1a + fracx * c2g * c2a;
l1 = ifracx * c3g * c3a + fracx * c4g * c4a;
gf = ifracy * l0 + fracy * l1;
// Blue
l0 = ifracx * c1b * c1a + fracx * c2b * c2a;
l1 = ifracx * c3b * c3a + fracx * c4b * c4a;
bf = ifracy * l0 + fracy * l1;
// Divide by alpha
if (a > 0)
{
rf = rf / a;
gf = gf / a;
bf = bf / a;
}
// Cast to byte
r = (byte)rf;
g = (byte)gf;
b = (byte)bf;
// Write destination
pd[srcIdx++] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
}
return pd;
}
/// <summary>
/// Rotates the bitmap in 90° steps clockwise and returns a new rotated XNAImage .
/// </summary>
/// <param name="bmp">The WriteableBitmap.</param>
/// <param name="angle">The angle in degress the bitmap should be rotated in 90° steps clockwise.</param>
/// <returns>A new WriteableBitmap that is a rotated version of the input.</returns>
public XNAImage Rotate(int angle)
{
// Use refs for faster access (really important!) speeds up a lot!
var w = width;
var h = height;
var p = pixels;
var i = 0;
XNAImage result = null;
angle %= 360;
if (angle > 0 && angle <= 90)
{
result = new XNAImage(h, w);
var rp = result.Pixels;
for (var x = 0; x < w; x++)
{
for (var y = h - 1; y >= 0; y--)
{
var srcInd = y * w + x;
rp[i] = p[srcInd];
i++;
}
}
}
else if (angle > 90 && angle <= 180)
{
result = new XNAImage(w, h);
var rp = result.Pixels;
for (var y = h - 1; y >= 0; y--)
{
for (var x = w - 1; x >= 0; x--)
{
var srcInd = y * w + x;
rp[i] = p[srcInd];
i++;
}
}
}
else if (angle > 180 && angle <= 270)
{
result = new XNAImage(h, w);
var rp = result.Pixels;
for (var x = w - 1; x >= 0; x--)
{
for (var y = 0; y < h; y++)
{
var srcInd = y * w + x;
rp[i] = p[srcInd];
i++;
}
}
}
else
{
result = Clone();
}
return result;
}
/// <summary>
/// Rotates the bitmap in any degree returns a new rotated XNAImage.
/// </summary>
/// <param name="angle">Arbitrary angle in 360 Degrees (positive = clockwise).</param>
/// <param name="crop">if true: keep the size, false: adjust canvas to new size</param>
/// <returns>A new XNAImage that is a rotated version of the input.</returns>
public XNAImage RotateFree(double angle)
{
return RotateFree(angle, true);
}
public XNAImage RotateFree(double angle, bool crop )
{
// rotating clockwise, so it's negative relative to Cartesian quadrants
double cnAngle = -1.0 * (Math.PI / 180) * angle;
// general iterators
int i, j;
// calculated indices in Cartesian coordinates
int x, y;
double fDistance, fPolarAngle;
// for use in neighbouring indices in Cartesian coordinates
int iFloorX, iCeilingX, iFloorY, iCeilingY;
// calculated indices in Cartesian coordinates with trailing decimals
double fTrueX, fTrueY;
// for interpolation
double fDeltaX, fDeltaY;
// pixel colours
Color clrTopLeft, clrTopRight, clrBottomLeft, clrBottomRight;
// interpolated "top" pixels
double fTopRed, fTopGreen, fTopBlue, fTopAlpha;
// interpolated "bottom" pixels
double fBottomRed, fBottomGreen, fBottomBlue, fBottomAlpha;
// final interpolated colour components
int iRed, iGreen, iBlue, iAlpha;
int iCentreX, iCentreY;
int iDestCentreX, iDestCentreY;
int iWidth, iHeight, newWidth, newHeight;
iWidth = width;
iHeight = height;
if (crop)
{
newWidth = iWidth;
newHeight = iHeight;
}
else
{
var rad = angle / (180 / Math.PI);
newWidth = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iHeight) + Math.Abs(Math.Cos(rad) * iWidth));
newHeight = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iWidth) + Math.Abs(Math.Cos(rad) * iHeight));
}
iCentreX = iWidth / 2;
iCentreY = iHeight / 2;
iDestCentreX = newWidth / 2;
iDestCentreY = newHeight / 2;
var bmBilinearInterpolation = new XNAImage(newWidth, newHeight);
var newp = bmBilinearInterpolation.Pixels;
var oldp = pixels;
var oldw = width;
// assigning pixels of destination image from source image
// with bilinear interpolation
for (i = 0; i < newHeight; ++i)
{
for (j = 0; j < newWidth; ++j)
{
// convert raster to Cartesian
x = j - iDestCentreX;
y = iDestCentreY - i;
// convert Cartesian to polar
fDistance = Math.Sqrt(x * x + y * y);
if (x == 0)
{
if (y == 0)
{
// centre of image, no rotation needed
newp[i * newWidth + j] = oldp[iCentreY * oldw + iCentreX];
continue;
}
if (y < 0)
{
fPolarAngle = 1.5 * Math.PI;
}
else
{
fPolarAngle = 0.5 * Math.PI;
}
}
else
{
fPolarAngle = Math.Atan2(y, x);
}
// the crucial rotation part
// "reverse" rotate, so minus instead of plus
fPolarAngle -= cnAngle;
// convert polar to Cartesian
fTrueX = fDistance * Math.Cos(fPolarAngle);
fTrueY = fDistance * Math.Sin(fPolarAngle);
// convert Cartesian to raster
fTrueX = fTrueX + iCentreX;
fTrueY = iCentreY - fTrueY;
iFloorX = (int)(Math.Floor(fTrueX));
iFloorY = (int)(Math.Floor(fTrueY));
iCeilingX = (int)(Math.Ceiling(fTrueX));
iCeilingY = (int)(Math.Ceiling(fTrueY));
// check bounds
if (iFloorX < 0 || iCeilingX < 0 || iFloorX >= iWidth || iCeilingX >= iWidth || iFloorY < 0 || iCeilingY < 0 || iFloorY >= iHeight || iCeilingY >= iHeight) continue;
fDeltaX = fTrueX - iFloorX;
fDeltaY = fTrueY - iFloorY;
clrTopLeft = GetPixel(iFloorX, iFloorY);
clrTopRight = GetPixel(iCeilingX, iFloorY);
clrBottomLeft = GetPixel(iFloorX, iCeilingY);
clrBottomRight = GetPixel(iCeilingX, iCeilingY);
// linearly interpolate horizontally between top neighbours
fTopRed = (1 - fDeltaX) * clrTopLeft.R + fDeltaX * clrTopRight.R;
fTopGreen = (1 - fDeltaX) * clrTopLeft.G + fDeltaX * clrTopRight.G;
fTopBlue = (1 - fDeltaX) * clrTopLeft.B + fDeltaX * clrTopRight.B;
fTopAlpha = (1 - fDeltaX) * clrTopLeft.A + fDeltaX * clrTopRight.A;
// linearly interpolate horizontally between bottom neighbours
fBottomRed = (1 - fDeltaX) * clrBottomLeft.R + fDeltaX * clrBottomRight.R;
fBottomGreen = (1 - fDeltaX) * clrBottomLeft.G + fDeltaX * clrBottomRight.G;
fBottomBlue = (1 - fDeltaX) * clrBottomLeft.B + fDeltaX * clrBottomRight.B;
fBottomAlpha = (1 - fDeltaX) * clrBottomLeft.A + fDeltaX * clrBottomRight.A;
// linearly interpolate vertically between top and bottom interpolated results
iRed = (int)(Math.Round((1 - fDeltaY) * fTopRed + fDeltaY * fBottomRed));
iGreen = (int)(Math.Round((1 - fDeltaY) * fTopGreen + fDeltaY * fBottomGreen));
iBlue = (int)(Math.Round((1 - fDeltaY) * fTopBlue + fDeltaY * fBottomBlue));
iAlpha = (int)(Math.Round((1 - fDeltaY) * fTopAlpha + fDeltaY * fBottomAlpha));
// make sure colour values are valid
if (iRed < 0) iRed = 0;
if (iRed > 255) iRed = 255;
if (iGreen < 0) iGreen = 0;
if (iGreen > 255) iGreen = 255;
if (iBlue < 0) iBlue = 0;
if (iBlue > 255) iBlue = 255;
if (iAlpha < 0) iAlpha = 0;
if (iAlpha > 255) iAlpha = 255;
var a = iAlpha + 1;
newp[i * newWidth + j] = (iAlpha << 24)
| ((byte)((iRed * a) >> 8) << 16)
| ((byte)((iGreen * a) >> 8) << 8)
| ((byte)((iBlue * a) >> 8));
}
}
return bmBilinearInterpolation;
}
/// <summary>
/// Flips (reflects the image) eiter vertical or horizontal.
/// </summary>
/// <param name="flipMode">The flip mode.</param>
/// <returns>A new XNAImage that is a flipped version of the input.</returns>
public XNAImage Flip(FlipMode flipMode)
{
// Use refs for faster access (really important!) speeds up a lot!
var w = width;
var h = height;
var p = pixels;
var i = 0;
XNAImage result = null;
if (flipMode == FlipMode.Horizontal)
{
result = new XNAImage(w, h);
var rp = result.Pixels;
for (var y = h - 1; y >= 0; y--)
{
for (var x = 0; x < w; x++)
{
var srcInd = y * w + x;
rp[i] = p[srcInd];
i++;
}
}
}
else if (flipMode == FlipMode.Vertical)
{
result = new XNAImage(w, h);
var rp = result.Pixels;
for (var y = 0; y < h; y++)
{
for (var x = w - 1; x >= 0; x--)
{
var srcInd = y * w + x;
rp[i] = p[srcInd];
i++;
}
}
}
return result;
}
}
}