hostile-takeover/m/TemplateTools.cs
2014-07-06 17:47:28 -07:00

672 lines
27 KiB
C#

using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using SpiffLib;
namespace m
{
/// <summary>
/// Summary description for TemplateTools.
/// </summary>
public class TemplateTools
{
public TemplateTools()
{
//
// TODO: Add constructor logic here
//
}
public static void ScaleTemplates(TemplateDoc tmpd, Size sizTile) {
// Scale templates
Template[] atmpl = tmpd.GetTemplates();
Template tmplBackground = tmpd.GetBackgroundTemplate();
tmpd.RemoveTemplates(atmpl);
foreach (Template tmpl in atmpl)
ScaleTemplate(tmpl, tmpd.TileSize, sizTile);
tmpd.TileSize = sizTile;
tmpd.AddTemplates(atmpl);
tmpd.SetBackgroundTemplate(tmplBackground);
}
#if false
static void ScaleTemplate(Template tmpl, Size sizTileSrc, Size sizTileDst) {
bool [,] afOccupancySrc = tmpl.OccupancyMap;
int ctx = afOccupancySrc.GetLength(1);
int cty = afOccupancySrc.GetLength(0);
Bitmap bmDst = new Bitmap(ctx * sizTileDst.Width, cty * sizTileDst.Height);
Graphics gDst = Graphics.FromImage(bmDst);
gDst.Clear(Color.FromArgb(255, 0, 255));
for (int tx = 0; tx < ctx; tx++) {
for (int ty = 0; ty < cty; ty++) {
if (!afOccupancySrc[ty, tx])
continue;
Rectangle rcSrc = new Rectangle(new Point(tx * sizTileSrc.Width, ty * sizTileSrc.Height), sizTileSrc);
Rectangle rcDst = new Rectangle(new Point(tx * sizTileDst.Width, ty * sizTileDst.Height), sizTileDst);
gDst.DrawImage(tmpl.Bitmap, rcDst, rcSrc, GraphicsUnit.Pixel);
}
}
gDst.Dispose();
bmDst.MakeTransparent(Color.FromArgb(255, 0, 255));
tmpl.Bitmap = bmDst;
}
#endif
static void ScaleTemplate(Template tmpl, Size sizTileSrc, Size sizTileDst) {
bool [,] afOccupancySrc = tmpl.OccupancyMap;
int ctx = afOccupancySrc.GetLength(1);
int cty = afOccupancySrc.GetLength(0);
Bitmap bmDst = new Bitmap(ctx * sizTileDst.Width, cty * sizTileDst.Height);
Graphics gDst = Graphics.FromImage(bmDst);
gDst.Clear(Color.FromArgb(255, 0, 255));
for (int tx = 0; tx < ctx; tx++) {
for (int ty = 0; ty < cty; ty++) {
if (!afOccupancySrc[ty, tx])
continue;
Rectangle rcSrc = new Rectangle(new Point(tx * sizTileSrc.Width, ty * sizTileSrc.Height), sizTileSrc);
Rectangle rcDst = new Rectangle(new Point(tx * sizTileDst.Width, ty * sizTileDst.Height), sizTileDst);
ScaleTile(tmpl.Bitmap, rcSrc, bmDst, rcDst);
}
}
gDst.Dispose();
MakeTransparent(bmDst);
tmpl.Bitmap = bmDst;
}
// HACK: Mono's MakeTransparent doesn't get the job done so we map the transparent color ourselves.
public static void MakeTransparent(Bitmap bm) {
bm.MakeTransparent(Color.FromArgb(255, 0, 255));
Color clrTransparentMarker = Color.FromArgb(0, 255, 0, 255);
Color clrTransparent = Color.FromArgb(0, 0, 0, 0);
for (int y = 0; y < bm.Height; y++) {
for (int x = 0; x < bm.Width; x++) {
Color clr = bm.GetPixel(x, y);
if (clr == clrTransparentMarker)
bm.SetPixel(x, y, clrTransparent);
}
}
}
static void ScaleTile(Bitmap bmSrc, Rectangle rcSrc, Bitmap bmDst, Rectangle rcDst) {
double nWidthRatio = (double)rcSrc.Width / (double)rcDst.Width;
double nHeightRatio = (double)rcSrc.Height / (double)rcDst.Height;
for (int yDst = rcDst.Top; yDst < rcDst.Bottom; yDst++) {
for (int xDst = rcDst.Left; xDst < rcDst.Right; xDst++) {
double xLeftSample = (double)xDst * nWidthRatio;
double yTopSample = (double)yDst * nHeightRatio;
double xRightSample = (double)(xDst + 1) * nWidthRatio;
double yBottomSample = (double)(yDst + 1) * nHeightRatio;
DoubleRect drcSample = new DoubleRect(
xLeftSample, yTopSample, xRightSample, yBottomSample);
Color clrSample = SampleBitmap(bmSrc, drcSample);
bmDst.SetPixel(xDst, yDst, clrSample);
}
}
}
static Color SampleBitmap(Bitmap bm, DoubleRect drc) {
double r = 0.0;
double g = 0.0;
double b = 0.0;
double nAreaTotal = drc.Width * drc.Height;
for (int y = (int)Math.Floor(drc.top); y < (int)Math.Ceiling(drc.bottom); y++) {
for (int x = (int)Math.Floor(drc.left); x < (int)Math.Ceiling(drc.right); x++) {
DoubleRect drcPixel = new DoubleRect(x, y, x + 1, y + 1);
drcPixel.Intersect(drc);
double nArea = drcPixel.Width * drcPixel.Height;
double nPercent = nArea / nAreaTotal;
Color clr = bm.GetPixel(x, y);
r += (double)clr.R / 255.0 * nPercent;
g += (double)clr.G / 255.0 * nPercent;
b += (double)clr.B / 255.0 * nPercent;
}
}
return Color.FromArgb((int)(r * 255.0), (int)(g * 255.0), (int)(b * 255.0));
}
public static TemplateDoc CloneTemplateDoc(TemplateDoc tmpdSrc) {
// This should probably be on ICloneable::Clone() on TemplateDoc
TemplateDoc tmpdDst = (TemplateDoc)DocManager.NewDocument(typeof(TemplateDoc), new Object[] { tmpdSrc.TileSize });
DocManager.SetActiveDocument(typeof(TemplateDoc), tmpdSrc);
Template[] atmplSrc = tmpdSrc.GetTemplates();
Template tmplSrcBackground = tmpdSrc.GetBackgroundTemplate();
Template tmplDstBackground = null;
ArrayList alsTmplDst = new ArrayList();
foreach (Template tmplSrc in atmplSrc) {
Template tmplDst = new Template(tmpdDst, tmplSrc.Name);
tmplDst.OccupancyMap = tmplSrc.OccupancyMap;
tmplDst.TerrainMap = tmplSrc.TerrainMap;
tmplDst.Bitmap = (Bitmap)tmplSrc.Bitmap.Clone();
alsTmplDst.Add(tmplDst);
if (tmplSrc == tmplSrcBackground)
tmplDstBackground = tmplDst;
}
if (tmplDstBackground != null)
tmpdDst.SetBackgroundTemplate(tmplDstBackground);
tmpdDst.AddTemplates((Template[])alsTmplDst.ToArray(typeof(Template)));
Palette palSrc = tmpdSrc.GetPalette();
if (palSrc != null)
tmpdDst.SetPalette(palSrc, false);
return tmpdDst;
}
public static void QuantizeTemplates(TemplateDoc tmpd, Palette palFixed, int cPalEntries, int cPalEntriesFixed, int cPalEntriesBackground) {
// Load the fixed palette. The result will be 24 bit templates normalized to a palette of 256 colors,
// the "fixed" palette plus a quantized palette.
if (palFixed == null) {
palFixed = Palette.OpenDialog(null);
if (palFixed == null) {
MessageBox.Show(DocManager.GetFrameParent(), "Must have the fixed color palette to continue!");
return;
}
switch (palFixed.Length) {
case 16:
cPalEntries = 16;
cPalEntriesFixed = 16;
cPalEntriesBackground = 0;
break;
case 256:
cPalEntries = 256;
cPalEntriesFixed = 128;
cPalEntriesBackground = 32;
break;
}
}
// Quantize loop. Designed to make optimal use of the lower 128 fixed colors
// Quantize background separately from foreground
Template tmplBackground = tmpd.GetBackgroundTemplate();
if (tmplBackground != null && cPalEntriesBackground != 0) {
// Create a despeckled hue map of the background. We'll use this to
// subtract background from foreground so we can quantize foreground separately
Bitmap bmHueBackground = MakeHueMap(tmplBackground.Bitmap);
DespeckleGrayscaleBitmap(bmHueBackground, 9, 50);
// Calc mean and standard deviation for filtering purposes
double nMean = CalcGrayscaleMean(bmHueBackground);
double nStdDev = CalcGrayscaleStandardDeviation(bmHueBackground, nMean);
// Add extract & quantize the background pixels
ArrayList alsColorsBackground = new ArrayList();
AddTemplateColors(alsColorsBackground, tmplBackground.Bitmap);
palFixed = QuantizeColors(alsColorsBackground, palFixed, cPalEntriesFixed + cPalEntriesBackground, cPalEntriesFixed);
cPalEntriesFixed += cPalEntriesBackground;
// Now extract foreground pixels by first subtracting background pixels
ArrayList alsColorsForeground = new ArrayList();
Template[] atmpl = tmpd.GetTemplates();
foreach (Template tmpl in atmpl) {
if (tmpl == tmplBackground)
continue;
Bitmap bmT = MakeHueMap(tmpl.Bitmap);
DespeckleGrayscaleBitmap(bmT, 9, 50);
SubtractGrayscaleDistribution(bmT, nMean, nStdDev);
for (int y = 0; y < bmT.Height; y++) {
for (int x = 0; x < bmT.Width; x++) {
Color clr = bmT.GetPixel(x, y);
if (clr != Color.FromArgb(255, 0, 255))
bmT.SetPixel(x, y, tmpl.Bitmap.GetPixel(x, y));
}
}
AddTemplateColors(alsColorsForeground, bmT);
}
// Now quantize foreground pixels
// Set the palette and color match
Palette palNew = QuantizeColors(alsColorsForeground, palFixed, cPalEntries, cPalEntriesFixed);
tmpd.SetPalette(palNew, true);
} else {
// No background template; just quantize everything together
Template[] atmpl = tmpd.GetTemplates();
ArrayList alsColors = new ArrayList();
foreach (Template tmpl in atmpl)
AddTemplateColors(alsColors, tmpl.Bitmap);
// Now quantize foreground pixels
// Set the palette and color match
Palette palNew = QuantizeColors(alsColors, palFixed, cPalEntries, cPalEntriesFixed);
tmpd.SetPalette(palNew, true);
}
}
static void AddTemplateColors(ArrayList alsColors, Bitmap bm) {
for (int y = 0; y < bm.Height; y++) {
for (int x = 0; x < bm.Width; x++) {
Color clr = bm.GetPixel(x, y);
if (clr == Color.FromArgb(255, 0, 255))
continue;
alsColors.Add(clr);
}
}
}
static Palette QuantizeColors(ArrayList alsColors, Palette palFixed, int cPalEntries, int cPalEntriesFixed) {
Palette palNew = null;
while (true) {
palNew = QuantizeColors2(alsColors, palFixed, cPalEntries, cPalEntriesFixed);
ArrayList alsColorsNew = new ArrayList();
foreach (Color clr in alsColors) {
int iclr = palNew.FindClosestEntry(clr);
if (iclr >= cPalEntriesFixed)
alsColorsNew.Add(clr);
}
if (alsColorsNew.Count == alsColors.Count)
break;
alsColors = alsColorsNew;
}
return palNew;
}
static Palette QuantizeColors2(ArrayList alsColors, Palette palFixed, int cPalEntries, int cPalEntriesFixed) {
// If no quantization needed (4 bit grayscale), return
if (cPalEntriesFixed >= cPalEntries)
return palFixed;
MedianCut mcut = new MedianCut(alsColors);
mcut.convert(cPalEntries - cPalEntriesFixed);
Palette palUpper = mcut.GetPalette();
palUpper.Pad(cPalEntries, Color.FromArgb(255, 0, 255));
Color[] aclr = new Color[cPalEntries];
for (int iclr = 0; iclr < cPalEntriesFixed; iclr++)
aclr[iclr] = palFixed[iclr];
for (int iclr = cPalEntriesFixed; iclr < cPalEntries; iclr++) {
Color clr = palUpper[iclr - cPalEntriesFixed];
Color clrT = Color.FromArgb(clr.R & 0xfc, clr.G & 0xfc, clr.B & 0xfc);
aclr[iclr] = clrT;
}
return new Palette(aclr);
}
public static Bitmap MakeHueMap(Bitmap bm) {
// Make grayscale image based on input image's hue
Bitmap bmHue = new Bitmap(bm);
for (int y = 0; y < bm.Height; y++) {
for (int x = 0; x < bm.Width; x++) {
Color clr = bm.GetPixel(x, y);
if (clr == Color.FromArgb(255, 0, 255)) {
bmHue.SetPixel(x, y, clr);
continue;
}
int nHue = (int)(clr.GetHue() / 360.0 * 255.0);
bmHue.SetPixel(x, y, Color.FromArgb(nHue, nHue, nHue));
}
}
return bmHue;
}
public static void DespeckleGrayscaleBitmap(Bitmap bm, int cSamples, int nThreshold) {
int[] anHues = new int[cSamples];
for (int y = 0; y < bm.Height; y++) {
for (int x = 0; x < bm.Width; x++) {
Color clr = bm.GetPixel(x, y);
if (clr == Color.FromArgb(255, 0, 255))
continue;
int nHue = PatternSampleGrayscaleBitmap(bm, x, y, nThreshold, anHues);
bm.SetPixel(x, y, Color.FromArgb(nHue, nHue, nHue));
}
}
}
public static int PatternSampleGrayscaleBitmap(Bitmap bm, int x, int y, int nThreshold, int[] anSamples) {
// Get the average - only needed for edge situations including edges next
// to transparent color
int cSamples = 0;
int nTotal = 0;
int nAverage = 0;
int cSamplesEdge = (int)Math.Sqrt(anSamples.Length);
if (cSamplesEdge < 3)
cSamplesEdge = 3;
int nDelta = cSamplesEdge / 2;
for (int yT = -nDelta ; yT <= nDelta; yT++) {
for (int xT = -nDelta; xT <= nDelta; xT++) {
if (xT == 0 && yT == 0)
continue;
if (x + xT >= 0 && x + xT < bm.Width && y + yT >= 0 && y + yT < bm.Height) {
Color clr = bm.GetPixel(x + xT, y + yT);
if (clr == Color.FromArgb(255, 0, 255))
continue;
cSamples++;
nTotal += clr.R;
}
}
}
nAverage = nTotal / cSamples;
// 5 sample filter (cross shape)
int nSample = 0;
if (anSamples.Length == 5) {
for (int yT = -1 ; yT <= 1; yT++) {
for (int xT = -1; xT <= 1; xT++) {
// If one of the corners, don't collect a sample
if (xT != 0 && yT != 0)
continue;
// If off an edge, use the average
if (x + xT < 0 || x + xT >= bm.Width || y + yT < 0 || y + yT >= bm.Height) {
anSamples[nSample++] = nAverage;
continue;
}
// If transparent color, use average
Color clr = bm.GetPixel(x + xT, y + yT);
if (clr == Color.FromArgb(255, 0, 255)) {
anSamples[nSample++] = nAverage;
continue;
}
// Get pixel
anSamples[nSample++] = clr.R;
}
}
} else {
// NxN sample filter. Needs to be odd, 3x3, 5x5, etc.
for (int yT = -nDelta; yT <= nDelta; yT++) {
for (int xT = -nDelta; xT <= nDelta; xT++) {
// If off an edge, use the average
if (x + xT < 0 || x + xT >= bm.Width || y + yT < 0 || y + yT >= bm.Height) {
anSamples[nSample++] = nAverage;
continue;
}
// If transparent color, use average
Color clr = bm.GetPixel(x + xT, y + yT);
if (clr == Color.FromArgb(255, 0, 255)) {
anSamples[nSample++] = nAverage;
continue;
}
// Get pixel
anSamples[nSample++] = clr.R;
}
}
}
// Sort values
Array.Sort(anSamples);
// Only accept median if the difference between it and the input pixel is above
// the specified threshold
int nInput = bm.GetPixel(x, y).R;
int nMedian = anSamples[anSamples.Length / 2];
if (Math.Abs(nMedian - nInput) < nThreshold)
return nInput;
return nMedian;
}
public static double CalcGrayscaleStandardDeviation(Bitmap bm, double nMean) {
// 68% within 1 standard deviation from mean
// 95% within 2 standard deviations from mean
// 99.7% within 3 standard deviations from mean
double nSigma = 0.0;
int cCounted = 0;
for (int y = 0; y < bm.Height; y++) {
for (int x = 0; x < bm.Width; x++) {
Color clr = bm.GetPixel(x, y);
if (clr == Color.FromArgb(255, 0, 255))
continue;
cCounted++;
double nDiff = clr.R - nMean;
nSigma += nDiff * nDiff;
}
}
return Math.Sqrt(nSigma / cCounted);
}
public static double CalcGrayscaleMean(Bitmap bm) {
double nHueTotal = 0.0;
int cCounted = 0;
for (int y = 0; y < bm.Height; y++) {
for (int x = 0; x < bm.Width; x++) {
Color clr = bm.GetPixel(x, y);
if (clr == Color.FromArgb(255, 0, 255))
continue;
cCounted++;
nHueTotal += clr.R;
}
}
return nHueTotal / cCounted;
}
public static void SubtractGrayscaleDistribution(Bitmap bm, double nMean, double nStdDev) {
double nMin = nMean - nStdDev * 2.0;
double nMax = nMean + nStdDev * 2.0;
for (int y = 0; y < bm.Height; y++) {
for (int x = 0; x < bm.Width; x++) {
Color clr = bm.GetPixel(x, y);
if (nMin <= (double)clr.R && (double)clr.R <= nMax)
bm.SetPixel(x, y, Color.FromArgb(255, 0, 255));
}
}
}
public static Bitmap ScaleTemplateBitmap(Bitmap bm, Bitmap bmMask, int cxNew, int cyNew, double nAreaBackgroundThreshold, double nLuminanceMultBackground, double nSaturationMultBackground, double nLuminanceMultForeground, double nSaturationMultForeground) {
Bitmap bmNew = new Bitmap(cxNew, cyNew);
for (int y = 0; y < bmNew.Height; y++) {
for (int x = 0; x < bmNew.Width; x++) {
double xLeft = (double)x * (double)bm.Width / (double)bmNew.Width;
double yTop = (double)y * (double)bm.Height / (double)bmNew.Height;
double xRight = (double)(x + 1) * (double)bm.Width / (double)bmNew.Width;
if (xRight > bm.Width)
xRight = bm.Width;
double yBottom = (double)(y + 1) * (double)bm.Height / (double)bmNew.Height;
if (yBottom > bm.Height)
yBottom = bm.Height;
DoubleRect drc = new DoubleRect(xLeft, yTop, xRight, yBottom);
Color clrSample = SampleTemplateBitmap(bm, bmMask, drc, nAreaBackgroundThreshold, nLuminanceMultBackground, nSaturationMultBackground, nLuminanceMultForeground, nSaturationMultForeground);
bmNew.SetPixel(x, y, clrSample);
}
}
return bmNew;
}
unsafe static Color SampleTemplateBitmap(Bitmap bm, Bitmap bmMask, DoubleRect drc, double nAreaBackgroundThreshold, double nLuminanceMultBackground, double nSaturationMultBackground, double nLuminanceMultForeground, double nSaturationMultForeground) {
// First classify as foreground or background. Calc percentages
double nAreaTotal = drc.Width * drc.Height;
double nAreaBackground = 0.0;
if (bmMask != null) {
for (int y = (int)Math.Floor(drc.top); y < (int)Math.Ceiling(drc.bottom); y++) {
for (int x = (int)Math.Floor(drc.left); x < (int)Math.Ceiling(drc.right); x++) {
// Calc the % of whole taken by this pixel fragment
DoubleRect drcPixel = new DoubleRect(x, y, x + 1.0, y + 1.0);
drcPixel.Intersect(drc);
double nAreaPixel = drcPixel.Width * drcPixel.Height;
// Get pixel
if (bmMask.GetPixel(x, y) == Color.FromArgb(255, 0, 255))
nAreaBackground += nAreaPixel;
}
}
}
// If background is above a threshold, this pixel will be background,
// otherwise foreground.
bool fBackground;
if (nAreaBackground / nAreaTotal >= nAreaBackgroundThreshold) {
fBackground = true;
nAreaTotal = nAreaBackground;
} else {
fBackground = false;
nAreaTotal -= nAreaBackground;
}
double r = 0;
double g = 0;
double b = 0;
for (int y = (int)Math.Floor(drc.top); y < (int)Math.Ceiling(drc.bottom); y++) {
for (int x = (int)Math.Floor(drc.left); x < (int)Math.Ceiling(drc.right); x++) {
// Foreground / background?
Color clr = bm.GetPixel(x, y);
if (bmMask != null) {
if (fBackground) {
if (bmMask.GetPixel(x, y) != Color.FromArgb(255, 0, 255))
continue;
} else {
if (bmMask.GetPixel(x, y) == Color.FromArgb(255, 0, 255))
continue;
}
}
// Calc the % of whole taken by this pixel fragment
DoubleRect drcPixel = new DoubleRect(x, y, x + 1.0, y + 1.0);
drcPixel.Intersect(drc);
double nAreaPixel = drcPixel.Width * drcPixel.Height;
double nPercentPixel = nAreaPixel / nAreaTotal;
// Add in the color components
r += clr.R * nPercentPixel;
g += clr.G * nPercentPixel;
b += clr.B * nPercentPixel;
}
}
// Tweak luminance & saturation
Color clrT = Color.FromArgb((int)r, (int)g, (int)b);
double nLuminance = clrT.GetBrightness() * (fBackground ? nLuminanceMultBackground : nLuminanceMultForeground);
if (nLuminance > 1.0)
nLuminance = 1.0;
double nSaturation = clrT.GetSaturation() * (fBackground ? nSaturationMultBackground : nSaturationMultForeground);
if (nSaturation > 1.0)
nSaturation = 1.0;
MyHSLtoRGB(clrT.GetHue(), nSaturation, nLuminance);
return Color.FromArgb((int)(rT * 255.0), (int)(gT * 255.0), (int)(bT * 255.0));
}
static double rT, gT, bT;
/*
* given h,s,l on [0..1],
* return r,g,b on [0..1]
*/
unsafe static void
HSL_to_RGB(double h, double sl, double l) {
double v;
v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl);
if (v <= 0) {
rT = gT = bT = 0.0;
} else {
double m;
double sv;
int sextant;
double fract, vsf, mid1, mid2;
m = l + l - v;
sv = (v - m ) / v;
h *= 6.0;
sextant = (int)h;
fract = h - sextant;
vsf = v * sv * fract;
mid1 = m + vsf;
mid2 = v - vsf;
switch (sextant) {
case 0: rT = v; gT = mid1; bT = m; break;
case 1: rT = mid2; gT = v; bT = m; break;
case 2: rT = m; gT = v; bT = mid1; break;
case 3: rT = m; gT = mid2; bT = v; break;
case 4: rT = mid1; gT = m; bT = v; break;
case 5: rT = v; gT = m; bT = mid2; break;
}
}
}
unsafe static void MyHSLtoRGB(double h, double s, double l) {
// From Graphics Gems. Convert Foley's 0..360 to 0..1
HSL_to_RGB(h / 360.0, s, l);
}
}
public struct DoubleRect {
public DoubleRect(double leftT, double topT, double rightT, double bottomT) {
left = leftT;
top = topT;
right = rightT;
bottom = bottomT;
}
public double Width {
get {
return right - left;
}
}
public double Height {
get {
return bottom - top;
}
}
public void Intersect(DoubleRect drc) {
left = Math.Max(left, drc.left);
right = Math.Min(right, drc.right);
if (left < right) {
top = Math.Max(top, drc.top);
bottom = Math.Min(bottom, drc.bottom);
if (top < bottom)
return;
}
left = 0.0;
right = 0.0;
top = 0.0;
bottom = 0.0;
}
public double left;
public double top;
public double right;
public double bottom;
}
}