using System.Drawing; using System.IO; using System.Drawing.Imaging; using System.Collections; namespace Icarus.Graphics.Animation { public class PazeraFramesetProvider : IFramesetProvider { /** * Exports the frameset. */ public void exportFrameset(Frameset frameset, string filename) { //First, we want to figure out the dimensions of the image in frames: int frameCount = frameset.getFrameCount(); double dimension = System.Math.Sqrt(frameCount); int width = (int) System.Math.Ceiling(dimension); int height = (int) System.Math.Ceiling(((double) frameCount) / ((double) width)); //Generate a matrix of frames to output: IFrame[,] frameMatrix = new IFrame[height,width]; for (int i = 0; i < frameCount; i++) { frameMatrix[i / width,i % width] = frameset.getFrameAt(i); } for (int i = frameCount; i < width * height; i++) { frameMatrix[i / width,i % width] = null; } //Now, precalculate the dimensions of each frame, taking the anchor point into consideration: Size[,] frameSizes = new Size[height,width]; for (int i = 0; i < frameCount; i++) { //Get the frame information: Point anch = frameset.getFrameAt(i).getAnchorPoint(); Size size = frameset.getFrameAt(i).GetSize(); //Adjust the size by the anchor point: if (anch.X < 0) { size.Width = size.Width - anch.X; } else if (anch.X >= size.Width) { size.Width = anch.X + 1; } if (anch.Y < 0) { size.Height = size.Height - anch.Y; } else if (anch.Y >= size.Height) { size.Height = anch.Y + 1; } frameSizes[i / width,i % width] = size; } for (int i = frameCount; i < width * height; i++) { frameSizes[i / width,i % width] = new Size(0, 0); } //Compute the widths of each column: int[] columnWidths = new int[width]; for (int i = 0; i < width; i++) { int w = 0; for (int j = 0; j < height; j++) { if (w < frameSizes[j,i].Width) { w = frameSizes[j,i].Width; } } columnWidths[i] = w; } //Compute the heights of each row: int[] rowHeights = new int[height]; for (int i = 0; i < height; i++) { int h = 0; for (int j = 0; j < width; j++) { if (h < frameSizes[i,j].Height) { h = frameSizes[i,j].Height; } } rowHeights[i] = h; } //Compute the total dimension of the image: int imageWidth = 1; for (int i = 0; i < width; i++) { imageWidth += columnWidths[i] + 1; } int imageHeight = 1; for (int i = 0; i < height; i++) { imageHeight += rowHeights[i] + 1; } //Generate the rectangles corresponding to each frame: Rectangle[] frameRectangles = new Rectangle[frameCount]; for (int i = 0; i < frameCount; i++) { int row = i / width; int col = i % width; int x = 1; if (col > 0) { x += frameRectangles[i - 1].Right; } int y = 1; if (row > 0) { y += frameRectangles[i - width].Bottom; } frameRectangles[i] = new Rectangle(x, y, columnWidths[col], rowHeights[row]); } //Sahweet, now we can start making the image: Bitmap output = new Bitmap(imageWidth, imageHeight); System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(output); //Set up control colors: Color colorBound = Color.FromArgb(255, 0, 255); Color colorInside = Color.FromArgb(0, 255, 0); Color colorAnchor = Color.FromArgb(0, 255, 255); Color colorOutside = Color.FromArgb(255, 255, 255); //Paint background with the bounds color: Brush outsideBrush = new SolidBrush(colorOutside); Brush boundBrush = new SolidBrush(colorBound); g.FillRectangle(boundBrush, 0, 0, imageWidth, imageHeight); //Iterate through the rectangles and paint images and their control patterns: for (int i = 0; i < frameCount; i++) { IFrame cf = frameset.getFrameAt(i); Point anchor = cf.getAnchorPoint(); Bitmap img = cf.getFrameImage(); int x = frameRectangles[i].X; int y = frameRectangles[i].Y; int startx = x; int starty = y; int anchx = x; int anchy = y; if (anchor.X < 0) { startx -= anchor.X; } else { anchx += anchor.X; } if (anchor.Y < 0) { starty -= anchor.Y; } else { anchy += anchor.Y; } //Paint inside / outside: //Horizontal: for (int j = x; j < startx; j++) { output.SetPixel(j, y - 1, colorOutside); } for (int j = startx; j < startx + img.Width; j++) { output.SetPixel(j, y - 1, colorInside); } for (int j = startx + img.Width; j < x + columnWidths[i % width]; j++) { output.SetPixel(j, y - 1, colorOutside); } //Vertical: for (int j = y; j < starty; j++) { output.SetPixel(x - 1, j, colorOutside); } for (int j = starty; j < starty + img.Height; j++) { output.SetPixel(x - 1, j, colorInside); } for (int j = starty + img.Height; j < y + rowHeights[i / width]; j++) { output.SetPixel(x - 1, j, colorOutside); } //Paint anchor points: output.SetPixel(anchx, y - 1, colorAnchor); output.SetPixel(x - 1, anchy, colorAnchor); //Render actual image: g.DrawImage(img, startx, starty); } //Render the remaining, unfilled frames: Rectangle previousRect = frameRectangles[frameCount - 1]; for (int i = frameCount; i < width * height; i++) { Rectangle curr = new Rectangle(previousRect.Right + 1, previousRect.Top, columnWidths[i % height], previousRect.Height); g.FillRectangle(outsideBrush, curr.X - 1, curr.Y, 1, curr.Height); g.FillRectangle(outsideBrush, curr.Left, curr.Y - 1, curr.Width, 1); } //Now we also need to render right-most blank lines: for (int i = 0; i < width; i++) { Rectangle cr = frameRectangles[i]; for (int j = cr.X; j < cr.Right; j++) { output.SetPixel(j, imageHeight - 1, colorOutside); } } for (int i = 0; i < height; i++) { Rectangle cr = frameRectangles[i * width]; for (int j = cr.Y; j < cr.Bottom; j++) { output.SetPixel(imageWidth - 1, j, colorOutside); } } //Also, render the control structure: output.SetPixel(imageWidth - 1, 1, colorInside); output.SetPixel(imageWidth - 1, 2, colorAnchor); //Remove ugly pink: //output.MakeTransparent(colorBound); //Save the image: string[] fileparts = filename.Split(new char[] {'.'}); string extension = fileparts[fileparts.Length - 1]; ImageFormat fmt = ImageFormat.Bmp; if (extension.Equals("gif", System.StringComparison.CurrentCultureIgnoreCase)) { fmt = ImageFormat.Gif; } else if (extension.Equals("jpg", System.StringComparison.CurrentCultureIgnoreCase) || extension.Equals("jpeg", System.StringComparison.CurrentCultureIgnoreCase)) { fmt = ImageFormat.Jpeg; } else if (extension.Equals("png", System.StringComparison.CurrentCultureIgnoreCase)) { fmt = ImageFormat.Png; } output.Save(filename, fmt); } /** * Goes through the image and extracts frames from it. */ public void fillFrameset(Frameset frameSet, Bitmap source) { //Initialize the control colors: ControlColors colors = new ControlColors(source); //Find frame bounds. var v_bounds = findVerticalBounds(source, colors); var h_bounds = findHorizontalBounds(source, colors); //Make rectangles that denote the bounds: var rectangles = boundsToRectangles(h_bounds, v_bounds); var i = rectangles.GetEnumerator(); while (i.MoveNext()) { Rectangle r = (Rectangle) i.Current; Rectangle realBounds = findRealBounds(source, colors, r); if (realBounds.Width < 1 || realBounds.Height < 1) { continue; } Point anchor = findAnchor(source, colors, r); //Adjust anchor coordinates to the current frame reference: anchor.X = anchor.X - realBounds.X; anchor.Y = anchor.Y - realBounds.Y; Bitmap frame = extractFrame(source, colors, realBounds); frame.MakeTransparent(colors.bound); frameSet.addFrame(frame, anchor); } } private IList findVerticalBounds(Bitmap source, ControlColors colors) { IList result = new ArrayList(); int width = source.Width; int index = 0; while (index < width) { index = firstHorizontalInstance(source, colors.bound, index, width, 0); result.Add(index); index++; } return result; } private IList findHorizontalBounds(Bitmap source, ControlColors colors) { IList result = new ArrayList(); int height = source.Height; int index = 0; while (index < height) { index = firstVerticalInstance(source, colors.bound, index, height, 0); result.Add(index); index++; } return result; } /** * Goes through the y-th row of the image, looking at x from start to stop, exclusive, and returns the first instance of color c, or stop if there is no such color in the row in the range. */ private int firstHorizontalInstance(Bitmap source, Color c, int start, int stop, int y) { int index = 0; for (index = start; index < stop; index++) { if (source.GetPixel(index, y).Equals(c)) { break; } } return index; } /** * Goes through the x-th column of the image, looking at y from start to stop, exclusive, and returns the first instance of color c, or stop if there is no such color in the column in the range. */ private int firstVerticalInstance(Bitmap source, Color c, int start, int stop, int x) { int index = 0; for (index = start; index < stop; index++) { if (source.GetPixel(x, index).Equals(c)) { break; } } return index; } private IList boundsToRectangles(IList horizontal, IList vertical) { IList result = new ArrayList(); for (int row = 0; row < horizontal.Count - 1; row ++) { for (int col = 0; col < vertical.Count - 1; col ++) { int left = (int) vertical[col]; int right = (int) vertical[col + 1]; int top = (int)horizontal[row]; int bottom = (int)horizontal[row + 1]; result.Add(new Rectangle(left, top, right - left, bottom - top)); } } return result; } private Point findAnchor(Bitmap source, ControlColors colors, Rectangle reference) { int x = firstHorizontalInstance(source, colors.anchor, reference.Left, reference.Right, reference.Top); int y = firstVerticalInstance(source, colors.anchor, reference.Top, reference.Bottom, reference.Left); return new Point(x, y); } private Bitmap extractFrame(Bitmap source, ControlColors colors, Rectangle reference) { //Make new result image: Bitmap result = new Bitmap(reference.Width, reference.Height); //Make graphics so we can render onto the image: System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(result); //Render: g.DrawImage( source, new Rectangle(0, 0, reference.Width, reference.Height), reference, GraphicsUnit.Pixel ); // g.DrawImage(source, -reference.X, -reference.Y, reference.Width, reference.Height); // DrawImage(source, -reference.X, -reference.Y); return result; } private Rectangle findRealBounds(Bitmap source, ControlColors colors, Rectangle reference) { int left = firstHorizontalInstance(source, colors.inside, reference.Left, reference.Right, reference.Top); int top = firstVerticalInstance(source, colors.inside, reference.Top, reference.Bottom, reference.Left); int right = firstHorizontalInstance(source, colors.outside, left, reference.Right, reference.Top); int bottom = firstVerticalInstance(source, colors.outside, top, reference.Bottom, reference.Left); return new Rectangle(left, top, right - left, bottom - top); } /** * This is just a little struct-like thing to simplify passing around the control colors. */ private class ControlColors { public Color bound; public Color inside; public Color anchor; public Color outside; public ControlColors(Bitmap img) { int width = img.Width; bound = img.GetPixel(width - 1, 0); inside = img.GetPixel(width - 1, 1); anchor = img.GetPixel(width - 1, 2); outside = img.GetPixel(width - 1, 3); } } } }