Files
TuxHunter/refactor/src/Icarus/Graphics/Animation/PazeraFramesetProvider.cs
2025-06-07 11:39:25 -04:00

402 lines
12 KiB
C#

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);
}
}
}
}