mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-16 12:08:36 +00:00
672 lines
27 KiB
C#
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;
|
|
}
|
|
}
|