/// <summary> /// Draws a line on an <see cref="GenericImage{T}"/>. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="x1"> /// The x position for the start of the line. /// </param> /// <param name="y1"> /// The y position for the start of the line. /// </param> /// <param name="x2"> /// The x position for the end of the line. /// </param> /// <param name="y2"> /// The y position for the end of the line. /// </param> /// <param name="value"> /// The value that the line will be drawn with. /// </param> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static void DrawLine <T>(this GenericImage <T> image, int x1, int y1, int x2, int y2, T value) { // source (converted from C) -> http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C int dx = Math.Abs(x2 - x1), sx = x1 < x2 ? 1 : -1; int dy = Math.Abs(y2 - y1), sy = y1 < y2 ? 1 : -1; int err = (dx > dy ? dx : -dy) / 2; while (true) { image[x1, y1] = value; if (x1 == x2 && y1 == y2) { break; } int e2 = err; if (e2 > -dx) { err -= dy; x1 += sx; } if (e2 < dy) { err += dx; y1 += sy; } } }
/// <summary> /// Rotates the image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The source image to rotate. /// </param> /// <param name="centerX"> /// The x position of the rotation axis. /// </param> /// <param name="centerY"> /// The y position of the rotation axis. /// </param> /// <param name="angle"> /// The angle in degrees to rotate the image. /// </param> /// <returns> /// Returns a new rotated image. /// </returns> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static GenericImage <T> Rotate <T>(this GenericImage <T> image, int centerX, int centerY, float angle) { // source (adapted for GenericImage) -> http://stackoverflow.com/a/11176961/341706 var radians = (Math.PI / 180) * angle; var cos = Math.Cos(radians); var sin = Math.Sin(radians); var newImage = new GenericImage <T>(image.Width, image.Height); for (var x = 0; x < image.Width; x++) { for (var y = 0; y < image.Height; y++) { var m = x - centerX; var n = y - centerY; var j = ((int)((m * cos) + (n * sin))) + centerX; var k = ((int)((n * cos) - (m * sin))) + centerY; if (j >= 0 && j < image.Width && k >= 0 && k < image.Height) { newImage[x, y] = image[j, k]; } } } return(newImage); }
/// <summary> /// Draws a <see cref="GenericImage{T}"/> on to a <see cref="Texture2D"/> texture. /// </summary> /// <param name="texture">A reference to a <see cref="Texture2D"/> type.</param> /// <param name="sourceImage">A reference to the <see cref="GenericImage{T}"/> that will be drawn.</param> /// <param name="x">The x position where the <see cref="sourceImage"/> will be drawn.</param> /// <param name="y">The y position where the <see cref="sourceImage"/> will be drawn.</param> /// <param name="sourceX">The source x position within <see cref="sourceImage"/>.</param> /// <param name="sourceY">The source y position within <see cref="sourceImage"/>.</param> /// <param name="sourceWidth">The source width within the <see cref="sourceImage"/>.</param> /// <param name="sourceHeight">The source height within the <see cref="sourceImage"/>.</param> /// <param name="flipHorizontally">If true will flip the <see cref="sourceImage"/> horizontally before drawing.</param> /// <param name="flipVertically">If true will flip the <see cref="sourceImage"/> vertically before drawing.</param> public static void Draw(this Texture2D texture, GenericImage <Color> sourceImage, int x, int y, int sourceX, int sourceY, int sourceWidth, int sourceHeight, bool flipHorizontally, bool flipVertically) { var textureRectangle = new Rect(0, 0, texture.width, texture.height); var sourceRectangle = new Rect(x, y, sourceWidth, sourceHeight); var intersect = textureRectangle.Intersect(sourceRectangle); if (!intersect.Intersects(new Rect(0, 0, sourceImage.Width, sourceImage.Height))) { return; } var tempImage = new GenericImage <Color>((int)intersect.width, (int)intersect.height); tempImage.Draw(sourceImage, 0, 0, sourceX, sourceY, tempImage.Width, tempImage.Height, (source, blendWith) => blendWith); if (flipHorizontally) { tempImage.FlipHorizontally(); } if (flipVertically) { tempImage.FlipVertically(); } var colors = tempImage.ToUnityColorArray(); texture.SetPixels(x, y, (int)intersect.width, (int)intersect.height, colors); texture.Apply(); }
/// <summary> /// Draws a circle. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="centerX"> /// The x center position of the circle. /// </param> /// <param name="centerY"> /// The y center position of the circle. /// </param> /// <param name="radius"> /// The radius of the circle. /// </param> /// <param name="color"> /// The value to use. /// </param> public static void DrawCircle <T>(this GenericImage <T> image, int centerX, int centerY, int radius, T color) { // source (converted from Java) -> http://rosettacode.org/wiki/Bitmap/Midpoint_circle_algorithm#Java var d = (5 - radius * 4) / 4; var x = 0; var y = radius; do { // ensure index is in range before setting (depends on your image implementation) // in this case we check if the pixel location is within the bounds of the image before setting the pixel image[centerX + x, centerY + y] = color; image[centerX + x, centerY - y] = color; image[centerX - x, centerY + y] = color; image[centerX - x, centerY - y] = color; image[centerX + y, centerY + x] = color; image[centerX + y, centerY - x] = color; image[centerX - y, centerY + x] = color; image[centerX - y, centerY - x] = color; if (d < 0) { d += 2 * x + 1; } else { d += 2 * (x - y) + 1; y--; } x++; }while (x <= y); }
/// <summary> /// Clears a image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The source image. /// </param> /// <param name="value"> /// The value to use for the clear. /// </param> public static void Clear <T>(this GenericImage <T> image, T value) { for (var x = 0; x < image.Width; x++) { for (var y = 0; y < image.Height; y++) { image[x, y] = value; } } }
/// <summary> /// Converts a <see cref="GenericImage{T}"/> to a <see cref="Texture2D"/> type. /// </summary> /// <param name="image">The image to be converted.</param> /// <returns>Returns a new <see cref="Texture2D"/> type.</returns> public static Texture2D ToTexture2D(this GenericImage <UnityEngine.Color> image) { var texture = new Texture2D(image.Width, image.Height, TextureFormat.ARGB32, false); var flippedImage = image.Clone(); flippedImage.FlipVertically(); texture.SetPixels32(flippedImage.ToUnityColor32Array()); texture.Apply(); return(texture); }
/// <summary> /// Sets the pixels of a <see cref="GenericImage{T}"/> type. /// </summary> /// <param name="image"> /// The image whose pixel data will be set. /// </param> /// <param name="pixelData"> /// The source pixel data. /// </param> /// <typeparam name="T"> /// The type of pixel data. /// </typeparam> /// <exception cref="ArgumentException"> /// If the pixel count for <see cref="pixelData"/> is not the same as the <see cref="image"/> pixel count. /// </exception> public static void SetPixels <T>(this GenericImage <T> image, T[] pixelData) { if (image.PixelGrid.Length != pixelData.Length) { throw new ArgumentException("pixelData"); } for (var i = 0; i < image.PixelGrid.Length; i++) { image.PixelGrid[i] = pixelData[i]; } }
/// <summary> /// Flips a image vertically. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// A reference to the image to be flipped. /// </param> /// <exception cref="NullReferenceException"> /// If the <see cref="image"/> parameter is null. /// </exception> public static void FlipVertically <T>(this GenericImage <T> image) { // TODO: Cloning is easy but memory intensive replace with proper code var pixels = image.Clone(); for (var y = 0; y < image.Height; y++) { for (var x = 0; x < image.Width; x++) { image[x, y] = pixels[x, pixels.Height - 1 - y]; } } }
/// <summary> /// Fills a circle. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="centerX"> /// The x center position of the circle. /// </param> /// <param name="centerY"> /// The y center position of the circle. /// </param> /// <param name="radius"> /// The radius of the circle. /// </param> /// <param name="color"> /// The value to use. /// </param> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static void FillCircle <T>(this GenericImage <T> image, int centerX, int centerY, int radius, T color) { // source -> http://www.dailyfreecode.com/code/fill-circle-scan-line-circle-fill-675.aspx var counter = centerY + radius; for (var count = centerY - radius; count <= counter; count++) { var x1 = (int)(centerX + Math.Sqrt((radius * radius) - ((count - centerY) * (count - centerY))) + 0.5); var x2 = (int)(centerX - Math.Sqrt((radius * radius) - ((count - centerY) * (count - centerY))) + 0.5); image.DrawLine(x1, count, x2, count, color); } }
/// <summary> /// Color tints an image. /// </summary> /// <param name="image"> /// The image to color tint. /// </param> /// <param name="color"> /// The color to use as the tint. /// </param> /// <param name="type"> /// The type to tint to perform. /// </param> /// <exception cref="ArgumentOutOfRangeException"> /// Unsupported type specified. /// </exception> public static void Tint(this GenericImage <Color> image, Color color, TintType type) { for (var indexX = 0; indexX < image.Width; indexX++) { for (var indexY = 0; indexY < image.Height; indexY++) { var sourceColor = image[indexX, indexY]; var r = sourceColor.R; var g = sourceColor.G; var b = sourceColor.B; var a = sourceColor.A; switch (type) { case TintType.Alpha: ////////////////////// ALPHA //////////////////////// sourceColor.R = (byte)((r * color.R) / 255); sourceColor.G = (byte)((g * color.G) / 255); sourceColor.B = (byte)((b * color.B) / 255); sourceColor.A = (byte)((a * color.A) / 255); break; case TintType.Multiply: /////////////////////// MULTIPLY ////////////////////////////// // Faster than a division like (s * d) / 255 are 2 shifts and 2 adds var ta = (a * color.A) + 128; var tr = (r * color.R) + 128; var tg = (g * color.G) + 128; var tb = (b * color.B) + 128; var ba = ((ta >> 8) + ta) >> 8; var br = ((tr >> 8) + tr) >> 8; var bg = ((tg >> 8) + tg) >> 8; var bb = ((tb >> 8) + tb) >> 8; sourceColor.R = (byte)ba; sourceColor.G = (byte)(ba <= br ? ba : br); sourceColor.B = (byte)(ba <= bg ? ba : bg); sourceColor.A = (byte)(ba <= bb ? ba : bb); break; default: throw new ArgumentOutOfRangeException("type"); } } } }
/// <summary> /// Clones an image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The source image. /// </param> /// <returns> /// Returns a new image containing the same data. /// </returns> public static GenericImage <T> Clone <T>(this GenericImage <T> image) { var newImage = new GenericImage <T>(image.Width, image.Height); for (var x = 0; x < image.Width; x++) { for (var y = 0; y < image.Height; y++) { newImage[x, y] = image[x, y]; } } return(newImage); }
/// <summary> /// Draws a string onto a <see cref="GenericImage{T}"/>. /// </summary> /// <param name="image">The image where the string will be drawn to.</param> /// <param name="font">The font to use when drawing the string.</param> /// <param name="text">The string to draw.</param> /// <param name="x">The x position where the string will be drawn at.</param> /// <param name="y">The y position where the string will be drawn at.</param> public static void DrawString(this GenericImage <Color> image, Font font, string text, float x, float y) { if (image == null) { throw new ArgumentNullException("image"); } if (font == null) { throw new ArgumentNullException("font"); } throw new NotImplementedException(); }
/// <summary> /// Converts a <see cref="GenericImage{T}"/> to an array of <see cref="Color32"/>. /// </summary> /// <param name="image">The image to be converted.</param> /// <returns>Returns a new array of <see cref="Color32"/>.</returns> public static Color32[] ToUnityColor32Array(this GenericImage <UnityEngine.Color> image) { var destination = new Color32[image.Width * image.Height]; var index = 0; for (var y = 0; y < image.Height; y++) { for (var x = 0; x < image.Width; x++) { destination[index++] = image[x, y]; } } return(destination); }
/// <summary> /// Draws a filled rectangle. /// </summary> /// <param name="image"> /// The destination image. /// </param> /// <param name="x"> /// The x position of the left side of the rectangle. /// </param> /// <param name="y"> /// The y position of the top side of the rectangle. /// </param> /// <param name="width"> /// The width of the rectangle. /// </param> /// <param name="height"> /// The height of the rectangle. /// </param> /// <param name="color"> /// The value to use. /// </param> public static void FillRectangle(this GenericImage <Color> image, int x, int y, int width, int height, Color color) { for (var indexY = 0; indexY < width; indexY++) { for (var indexX = 0; indexX < height; indexX++) { if (indexY + x > image.Width - 1 || indexX + y > image.Height - 1 || indexY + x < -width || indexX + y < -height) { continue; } image[indexY + x, indexX + y] = image[indexY + x, indexX + y].Blend(color); } } }
/// <summary> /// Draws a filled rectangle. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="x"> /// The x position of the left side of the rectangle. /// </param> /// <param name="y"> /// The y position of the top side of the rectangle. /// </param> /// <param name="width"> /// The width of the rectangle. /// </param> /// <param name="height"> /// The height of the rectangle. /// </param> /// <param name="value"> /// The value to use. /// </param> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static void FillRectangle <T>(this GenericImage <T> image, int x, int y, int width, int height, T value) { for (var indexY = 0; indexY < width; indexY++) { for (var indexX = 0; indexX < height; indexX++) { if (indexY + x > image.Width - 1 || indexX + y > image.Height - 1 || indexY + x < -width || indexX + y < -height) { continue; } image[indexY + x, indexX + y] = value; } } }
/// <summary> /// Converts a <see cref="GenericImage{T}"/> to an array of <see cref="UnityEngine.Color"/>. /// </summary> /// <param name="image">The image to be converted.</param> /// <returns>Returns a new array of <see cref="UnityEngine.Color"/>.</returns> public static UnityEngine.Color[] ToUnityColorArray(this GenericImage <Color> image) { var colorArray = new UnityEngine.Color[image.Width * image.Height]; var position = 0; for (var y = 0; y < image.Height; y++) { for (var x = 0; x < image.Width; x++) { colorArray[position++] = image[x, y]; } } return(colorArray); }
/// <summary> /// Converts a <see cref="Texture2D"/> to a <see cref="GenericImage{T}"/> type. /// </summary> /// <param name="image">The image to be converted.</param> /// <returns>Returns a new <see cref="GenericImage{T}"/> type.</returns> /// <remarks>The source Texture2D image must be readable.</remarks> public static GenericImage <Color> ToGenericImage(this Texture2D image) { var colors = image.GetPixels32(0); var texture = new GenericImage <Color>(image.width, image.height); var index = 0; for (var y = texture.Height - 1; y >= 0; y--) { for (var x = 0; x < texture.Width; x++) { texture[x, y] = colors[index++]; } } return(texture); }
/// <summary> /// Scales a image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The source image. /// </param> /// <param name="x"> /// The horizontal scale. /// </param> /// <param name="y"> /// The vertical scale. /// </param> /// <returns> /// Returns a new scaled image. /// </returns> /// <exception cref="ArgumentOutOfRangeException"> /// x and y values must be greater then 0. /// </exception> public static GenericImage <T> Scale <T>(this GenericImage <T> image, float x, float y) { if (x <= 0) { throw new ArgumentOutOfRangeException("x"); } if (y <= 0) { throw new ArgumentOutOfRangeException("y"); } if (Math.Abs(x - 1.0f) < 0 && Math.Abs(y - 1.0f) < 0) { return(image.Clone()); } var width = (int)(image.Width * x); var height = (int)(image.Height * y); if (width < 1) { width = 1; } if (height < 1) { height = 1; } var scaled = new GenericImage <T>(width, height); for (var indexY = 0; indexY < scaled.Height; indexY++) { for (var indexX = 0; indexX < scaled.Width; indexX++) { var u = scaled.Width == 1 ? 1 : indexX / (float)(scaled.Width - 1); var v = scaled.Height == 1 ? 1 : indexY / (float)(scaled.Height - 1); scaled[indexX, indexY] = image[(int)Math.Round(u * (image.Width - 1)), (int)Math.Round(v * (image.Height - 1))]; } } return(scaled); }
/// <summary> /// Converts a materials <see cref="Material.mainTexture"/> property into a <see cref="GenericImage{T}"/> type takes into account the /// materials <see cref="Material.mainTextureOffset"/> and <see cref="Material.mainTextureScale"/> properties. See remarks. /// </summary> /// <param name="material">The source material to get the <see cref="Material.mainTexture"/> reference from.</param> /// <returns>Returns a <see cref="GenericImage{T}"/> type representing the materials texture co-ordinates.</returns> /// <remarks>This method does not return the original texture specified by the <see cref="Material.mainTexture"/> property but rather /// takes into account the materials <see cref="Material.mainTextureOffset"/> and <see cref="Material.mainTextureScale"/> properties /// in order to generate a <see cref="GenericImage{T}"/> type.</remarks> /// <exception cref="InvalidCastException">If <see cref="Material.mainTexture"/> cannot be cast to a <see cref="Texture2D"/> type.</exception> /// <exception cref="ArgumentNullException">If the <see cref="material"/> parameter is null.</exception> public static GenericImage <Color> ToGenericImage(this Material material) { // ensure material specified if (material == null) { throw new ArgumentNullException("material"); } // if no main texture then also return null if (material.mainTexture == null) { return(null); } var texture = material.mainTexture as Texture2D; // ensure material texture is a Texture2D type if (texture == null) { throw new InvalidCastException("Material texture is not a Texture2D!"); } // create a generic image. var image = texture.CreateGenericImage(); var data = image.ToUnityColor32Array(); var tempImage = new GenericImage <Color>(image.Width, image.Height); tempImage.SetPixels(Array.ConvertAll(data, input => new Color(input.r, input.g, input.b, input.a))); var offset = material.mainTextureOffset; var position = new Point( (int)((offset.x * texture.width) % texture.width), (int)(texture.height - ((offset.y * texture.height) % texture.height))); var scale = material.mainTextureScale; var size = new Size((int)(scale.x * texture.width), (int)(scale.y * texture.height)); var subImage = new GenericImage <Color>(size.Width, size.Height); subImage.Draw(tempImage, 0, 0, position.X, position.Y - size.Height, size.Width, size.Height, (source, blendWith) => blendWith); // convert and return a generic image return(subImage); }
/// <summary> /// Fills an area with a checkerboard pattern. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image">The source image.</param> /// <param name="x">The x position within the <see cref="image"/>.</param> /// <param name="y">The y position within the <see cref="image"/>.</param> /// <param name="width">The width of the checkerboard.</param> /// <param name="height">The height of the checkerboard.</param> /// <param name="colorA">The first checkerboard fill value.</param> /// <param name="colorB">The second checkerboard fill value.</param> /// <param name="size">The size of the checkerboard pattern.</param> /// <exception cref="ArgumentOutOfRangeException">If <see cref="size"/> is less then 1.</exception> public static void Checkerboard <T>(this GenericImage <T> image, int x, int y, int width, int height, T colorA, T colorB, int size) { if (size < 1) { throw new ArgumentOutOfRangeException("size"); } var half = size / 2; for (var indexY = 0; indexY < height; indexY += size) { for (var indexX = 0; indexX < width; indexX += size) { image.FillRectangle(x + indexX, y + indexY, half, half, colorB); image.FillRectangle(x + indexX + half, y + indexY, half, half, colorA); image.FillRectangle(x + indexX + half, y + indexY + half, half, half, colorB); image.FillRectangle(x + indexX, y + indexY + half, half, half, colorA); } } }
/// <summary> /// Draws a filled Ellipse. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="x"> /// The x left most position of the ellipse. /// </param> /// <param name="y"> /// The y top most position of the ellipse. /// </param> /// <param name="width"> /// The width of the ellipse. /// </param> /// <param name="height"> /// The height of the ellipse. /// </param> /// <param name="color"> /// The value to use. /// </param> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static void FillEllipse <T>(this GenericImage <T> image, int x, int y, int width, int height, T color) { width = width / 2; height = height / 2; var centerX = x + width; var centerY = y + height; // source -> http://stackoverflow.com/questions/10322341/simple-algorithm-for-drawing-filled-ellipse-in-c-c for (var indexY = -height; indexY <= height; indexY++) { for (var indexX = -width; indexX <= width; indexX++) { var dx = indexX / (double)width; var dy = indexY / (double)height; if ((dx * dx) + (dy * dy) <= 1) { image[centerX + indexX, centerY + indexY] = color; } } } }
/// <summary> /// Generates a normal map. /// </summary> /// <param name="image"> /// The source image to generate the normal map from. /// </param> /// <returns> /// Returns a new <see cref="GenericImage{T}"/> containing the normal map. /// </returns> public static GenericImage <Color> NormalMap(this GenericImage <Color> image) { var newImage = new GenericImage <Color>(image.Width, image.Height); for (var y = 0; y < image.Height; y++) { for (var x = 0; x < image.Width; ++x) { var tempPixel = image[x, y]; var tempVectorX = tempPixel.R / 255.0f; var tempVectorY = tempPixel.R / 255.0f; var tempVectorZ = 1.0f; Normalize(tempVectorX, tempVectorY, tempVectorZ, out tempVectorX, out tempVectorY, out tempVectorZ); tempVectorX = ((tempVectorX + 1.0f) / 2.0f) * 255.0f; tempVectorY = ((tempVectorY + 1.0f) / 2.0f) * 255.0f; tempVectorZ = ((tempVectorZ + 1.0f) / 2.0f) * 255.0f; newImage[x, y] = new Color((byte)tempVectorX, (byte)tempVectorY, (byte)tempVectorZ, 255); } } return(newImage); }
/// <summary> /// Rotates the image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The source image to rotate. /// </param> /// <param name="angle"> /// The angle in degrees to rotate the image. /// </param> /// <returns> /// Returns a new rotated image. /// </returns> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static GenericImage <T> Rotate <T>(this GenericImage <T> image, float angle) { return(Rotate(image, image.Width / 2, image.Height / 2, angle)); }
/// <summary> /// Converts image pixel data to a byte array. /// </summary> /// <param name="image"> /// The source image. /// </param> /// <param name="x"> /// The x position of the left side of the rectangle. /// </param> /// <param name="y"> /// The y position of the top side of the rectangle. /// </param> /// <param name="width"> /// The width of the rectangle. /// </param> /// <param name="height"> /// The height of the rectangle. /// </param> /// <returns> /// Returns an array of bytes containing the pixel data. /// </returns> /// <exception cref="ArgumentOutOfRangeException"> /// If x, y is out of bounds of the images dimensions. The width or height is less then 1. /// </exception> /// <exception cref="ArgumentException"> /// Clipped width or height is less then 1. /// </exception> public static byte[] ToByteArray(this GenericImage <Color> image, int x, int y, int width, int height) { if (x > image.Width - 1) { throw new ArgumentOutOfRangeException("x"); } if (y > image.Height - 1) { throw new ArgumentOutOfRangeException("y"); } if (width < 1) { throw new ArgumentOutOfRangeException("width"); } if (height < 1) { throw new ArgumentOutOfRangeException("height"); } if (x + width - 1 > image.Width - 1) { width = image.Width - 1 - x; } if (y + height - 1 > image.Height - 1) { height = image.Height - 1 - y; } if (width < 1) { throw new ArgumentException(); } if (height < 1) { throw new ArgumentException(); } if (x < 0) { width = x + width; x = 0; } if (y < 0) { height = y + height; y = 0; } if (width < 1) { throw new ArgumentException(); } if (height < 1) { throw new ArgumentException(); } var data = new byte[width * height * 4]; var position = 0; for (var indexY = 0; indexY < height; indexY++) { for (var indexX = 0; indexX < width; indexX++) { var positionY = indexY + y; var positionX = indexX + x; var color = image[positionX, positionY]; data[position++] = color.R; data[position++] = color.G; data[position++] = color.B; data[position++] = color.A; } } return(data); }
/// <summary> /// Draws a image within another image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="source"> /// The source image. /// </param> /// <param name="x"> /// The x position in the destination image. /// </param> /// <param name="y"> /// The y position in the destination image. /// </param> /// <param name="width"> /// The destination width of the drawn image. /// </param> /// <param name="height"> /// The destination height of the drawn image. /// </param> /// <param name="blendCallback"> /// A <see cref="Func{TResult}"/> callback that is used to perform pixel blending. /// </param> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static void Draw <T>(this GenericImage <T> image, GenericImage <T> source, int x, int y, int width, int height, Func <T, T, T> blendCallback) { Draw(image, source, x, y, width, height, 0, 0, source.Width, source.Height, blendCallback); }
/// <summary> /// Draws a image within another image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="source"> /// The source image. /// </param> /// <param name="x"> /// The x position in the destination image. /// </param> /// <param name="y"> /// The y position in the destination image. /// </param> /// <param name="sourceX"> /// The x position in the source image. /// </param> /// <param name="sourceY"> /// The y position in the source image. /// </param> /// <param name="sourceWidth"> /// The source width. /// </param> /// <param name="sourceHeight"> /// The source height. /// </param> /// <param name="blendCallback"> /// A <see cref="Func{TResult}"/> callback that is used to perform pixel blending. /// </param> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static void Draw <T>(this GenericImage <T> image, GenericImage <T> source, int x, int y, int sourceX, int sourceY, int sourceWidth, int sourceHeight, Func <T, T, T> blendCallback) { Draw(image, source, x, y, sourceWidth, sourceHeight, sourceX, sourceY, sourceWidth, sourceHeight, blendCallback); }
/// <summary> /// Draws a image within another image. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="source"> /// The source image. /// </param> /// <param name="x"> /// The x position in the destination image. /// </param> /// <param name="y"> /// The y position in the destination image. /// </param> /// <param name="width"> /// The destination width of the drawn image. /// </param> /// <param name="height"> /// The destination height of the drawn image. /// </param> /// <param name="sourceX"> /// The x position in the source image. /// </param> /// <param name="sourceY"> /// The y position in the source image. /// </param> /// <param name="sourceWidth"> /// The source width. /// </param> /// <param name="sourceHeight"> /// The source height. /// </param> /// <param name="blendCallback"> /// A <see cref="Func{TResult}"/> callback that is used to perform pixel blending. /// </param> /// <exception cref="ArgumentNullException"> /// If <see cref="source"/> or <see cref="blendCallback"/> is null. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// If width, height, sourceWidth or sourceHeight are less then 1. /// </exception> /// <typeparamref name="T"> /// The type representing the pixel data.</typeparamref> public static void Draw <T>(this GenericImage <T> image, GenericImage <T> source, int x, int y, int width, int height, int sourceX, int sourceY, int sourceWidth, int sourceHeight, Func <T, T, T> blendCallback) { // perform input validation if (source == null) { throw new ArgumentNullException("source"); } if (blendCallback == null) { throw new ArgumentNullException("blendCallback"); } if (sourceWidth < 1) { throw new ArgumentOutOfRangeException("sourceWidth"); } if (sourceHeight < 1) { throw new ArgumentOutOfRangeException("sourceHeight"); } if (width < 1) { throw new ArgumentOutOfRangeException("width"); } if (height < 1) { throw new ArgumentOutOfRangeException("height"); } var scaledWidth = (float)width / sourceWidth; var scaledHeight = (float)height / sourceHeight; if (x > image.Width - 1 || y > image.Height - 1 || sourceX > source.Width - 1 || sourceY > source.Height - 1) { return; } // copy source data to temp image var temporaryImage = new GenericImage <T>(sourceWidth, sourceHeight); for (var indexY = 0; indexY < sourceHeight; indexY++) { for (var indexX = 0; indexX < sourceWidth; indexX++) { var positionX = indexX + sourceX; var positionY = indexY + sourceY; // TODO: this needs to be better optimized. We are processing all pixels even though some of // the pixels could be cropped off. Need to calculate the rectangle the copy pixels and process only that rectangle if (positionX > source.Width - 1 || positionY > source.Height - 1 || positionX < 0 || positionY < 0) { continue; } temporaryImage[indexX, indexY] = source[positionX, positionY]; } } var scaled = temporaryImage.Scale(scaledWidth, scaledHeight); if (x < -scaled.Width - 1 || y < -scaled.Height - 1) { return; } for (var indexY = 0; indexY < scaled.Height; indexY++) { for (var indexX = 0; indexX < scaled.Width; indexX++) { var destinationX = indexX + x; var destinationY = indexY + y; // TODO: this needs to be better optimized. We are processing all pixels even though some of // the pixels could be cropped off. Need to calculate the rectangle the copy pixels and process only that rectangle if (destinationX > image.Width - 1 || destinationY > image.Height - 1 || destinationX < 0 || destinationY < 0) { continue; } var value = scaled[indexX, indexY]; image[destinationX, destinationY] = blendCallback(image[destinationX, destinationY], value); } } }
/// <summary> /// Draws a Ellipse. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="centerX"> /// The x center position of the circle. /// </param> /// <param name="centerY"> /// The y center position of the circle. /// </param> /// <param name="width"> /// The width of the Ellipse. /// </param> /// <param name="height"> /// The height of the Ellipse. /// </param> /// <param name="color"> /// The value to use. /// </param> public static void DrawEllipse <T>(this GenericImage <T> image, int centerX, int centerY, int width, int height, T color) { // source (converted from C++) -> http://www.dailyfreecode.com/Code/draw-ellipse-midpoint-ellipse-algorithm-714.aspx float aa = width * width; float bb = height * height; float aa2 = aa * 2; float bb2 = bb * 2; float x = 0; float y = height; float fx = 0; float fy = aa2 * height; float p = (int)(bb - (aa * height) + (0.25 * aa) + 0.5); image[(int)(centerX + x), (int)(centerY + y)] = color; image[(int)(centerX + x), (int)(centerY - y)] = color; image[(int)(centerX - x), (int)(centerY - y)] = color; image[(int)(centerX - x), (int)(centerY + y)] = color; while (fx < fy) { x++; fx += bb2; if (p < 0) { p += fx + bb; } else { y--; fy -= aa2; p += fx + bb - fy; } image[(int)(centerX + x), (int)(centerY + y)] = color; image[(int)(centerX + x), (int)(centerY - y)] = color; image[(int)(centerX - x), (int)(centerY - y)] = color; image[(int)(centerX - x), (int)(centerY + y)] = color; } p = (int)((bb * (x + 0.5) * (x + 0.5)) + (aa * (y - 1) * (y - 1)) - (aa * bb) + 0.5); while (y > 0) { y--; fy -= aa2; if (p >= 0) { p += aa - fy; } else { x++; fx += bb2; p += fx + aa - fy; } image[(int)(centerX + x), (int)(centerY + y)] = color; image[(int)(centerX + x), (int)(centerY - y)] = color; image[(int)(centerX - x), (int)(centerY - y)] = color; image[(int)(centerX - x), (int)(centerY + y)] = color; } }
/// <summary> /// Draws a rectangle. /// </summary> /// <param name="image"> /// The destination image. /// </param> /// <param name="x"> /// The x position of the left side of the rectangle. /// </param> /// <param name="y"> /// The y position of the top side of the rectangle. /// </param> /// <param name="width"> /// The width of the rectangle. /// </param> /// <param name="height"> /// The height of the rectangle. /// </param> /// <param name="value"> /// The value to use. /// </param> public static void DrawRectangle(this GenericImage <Color> image, int x, int y, int width, int height, Color value) { DrawRectangle(image, x, y, width, height, value, (source, blendWith) => source.Blend(blendWith)); }
/// <summary> /// Draws a rectangle. /// </summary> /// <typeparam name="T"> /// Specifies the type use for pixel data. /// </typeparam> /// <param name="image"> /// The destination image. /// </param> /// <param name="x"> /// The x position of the left side of the rectangle. /// </param> /// <param name="y"> /// The y position of the top side of the rectangle. /// </param> /// <param name="width"> /// The width of the rectangle. /// </param> /// <param name="height"> /// The height of the rectangle. /// </param> /// <param name="value"> /// The value to use. /// </param> /// <param name="blendCallback"> /// A <see cref="Func{TResult}"/> callback that is used to perform pixel blending. /// </param> /// <exception cref="ArgumentNullException"> /// If <see cref="blendCallback"/> is null. /// </exception> public static void DrawRectangle <T>(this GenericImage <T> image, int x, int y, int width, int height, T value, Func <T, T, T> blendCallback) { if (blendCallback == null) { throw new ArgumentNullException("blendCallback"); } var bottom = y + height - 1; var right = x + width - 1; // allows negative widths if (x > right) { var temp = right; right = x; x = temp; } // allows negative heights if (y > bottom) { var temp = bottom; bottom = y; y = temp; } // top if (y > -1 && y < image.Height) { var leftValue = x < 0 ? 0 : x; var rightValue = right > image.Width - 1 ? image.Width - 1 : right; for (var i = leftValue; i <= rightValue; i++) { image[i, y] = blendCallback(image[i, y], value); } } // bottom if (bottom > -1 && bottom < image.Height) { var leftValue = x < 0 ? 0 : x; var rightValue = right > image.Width - 1 ? image.Width - 1 : right; for (var i = leftValue; i <= rightValue; i++) { image[i, bottom] = blendCallback(image[i, bottom], value); } } // left if (x > -1 && x < image.Width) { var topValue = y < 0 ? 0 : y; var bottomValue = bottom > image.Height - 1 ? image.Height - 1 : bottom; for (var i = topValue; i <= bottomValue; i++) { image[x, i] = blendCallback(image[x, i], value); } } // right if (right > -1 && right < image.Width) { var topValue = y < 0 ? 0 : y; var bottomValue = bottom > image.Height - 1 ? image.Height - 1 : bottom; for (var i = topValue; i <= bottomValue; i++) { image[right, i] = blendCallback(image[right, i], value); } } }