Click here to Skip to main content
15,886,199 members
Articles / Mobile Apps / Windows Phone 7

Windows Phone: Are you Game? Part 1

Rate me:
Please Sign up or sign in to vote.
4.77/5 (18 votes)
12 Nov 2011CPOL9 min read 46.1K   693   36  
Introduction to XNA game development for Windows Phone - Includes XNAImage, image manipulation for XNA
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;
    }
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Sea Surveillance AS
Norway Norway
Chief Architect - Sea Surveillance AS.

Specializing in integrated operations and high performance computing solutions.

I’ve been fooling around with computers since the early eighties, I’ve even done work on CP/M and MP/M.

Wrote my first “real” program on a BBC micro model B based on a series in a magazine at that time. It was fun and I got hooked on this thing called programming ...

A few Highlights:

  • High performance application server development
  • Model Driven Architecture and Code generators
  • Real-Time Distributed Solutions
  • C, C++, C#, Java, TSQL, PL/SQL, Delphi, ActionScript, Perl, Rexx
  • Microsoft SQL Server, Oracle RDBMS, IBM DB2, PostGreSQL
  • AMQP, Apache qpid, RabbitMQ, Microsoft Message Queuing, IBM WebSphereMQ, Oracle TuxidoMQ
  • Oracle WebLogic, IBM WebSphere
  • Corba, COM, DCE, WCF
  • AspenTech InfoPlus.21(IP21), OsiSoft PI


More information about what I do for a living can be found at: harlinn.com or LinkedIn

You can contact me at espen@harlinn.no

Comments and Discussions