From d18bd0a4d7cd4e2fd57135e71f05a9d508cf735c Mon Sep 17 00:00:00 2001 From: "kristopher.j.griffin" Date: Sat, 29 May 2021 15:55:13 -0400 Subject: [PATCH] add new filterslides and corresponding image filters --- Xenon/Compiler/AST/XenonASTFilterImage.cs | 198 ++++++++++++- Xenon/Compiler/LanguageKeywords.cs | 29 ++ Xenon/Compiler/Lexer.cs | 2 + Xenon/Renderer/ImageFilters/ImageFilter.cs | 5 +- .../ImageFilters/ImageFilterParams.cs | 72 ++++- .../ImageFilters/ImageFilterRenderer.cs | 12 +- Xenon/Renderer/ImageFilters/ImageFilters.cs | 275 ++++++++++++++++++ 7 files changed, 580 insertions(+), 13 deletions(-) diff --git a/Xenon/Compiler/AST/XenonASTFilterImage.cs b/Xenon/Compiler/AST/XenonASTFilterImage.cs index 501af411..543bf014 100644 --- a/Xenon/Compiler/AST/XenonASTFilterImage.cs +++ b/Xenon/Compiler/AST/XenonASTFilterImage.cs @@ -11,9 +11,11 @@ namespace Xenon.Compiler { class XenonASTFilterImage : IXenonASTCommand { - public string AssetName { get; set; } public List<(ImageFilter Type, ImageFilterParams FParams)> Filters { get; set; } = new List<(ImageFilter Type, ImageFilterParams)>(); + private Dictionary assetstoresolve = new Dictionary(); + private int assetids = 0; + public IXenonASTElement Compile(Lexer Lexer, XenonErrorLogger Logger) { XenonASTFilterImage filterimage = new XenonASTFilterImage(); @@ -42,7 +44,23 @@ public IXenonASTElement Compile(Lexer Lexer, XenonErrorLogger Logger) { CompileFilterCommand_crop(Lexer, Logger); } - + if (filtername == "centerassetfill") + { + CompileFilterCommand_centerassetfill(Lexer, Logger); + } + if (filtername == "uniformstretch") + { + CompileFilterCommand_uniformstetch(Lexer, Logger); + } + if (filtername == "centeronbackground") + { + CompileFilterCommand_centeronbackground(Lexer, Logger); + } + if (filtername == "coloredit") + { + CompileFilterCommand_coloredit(Lexer, Logger); + } + Lexer.GobbleWhitespace(); } Lexer.GobbleWhitespace(); @@ -163,6 +181,163 @@ private void CompileFilterCommand_solidcolorcanvas(Lexer Lexer, XenonErrorLogger } + private void CompileFilterCommand_centerassetfill(Lexer Lexer, XenonErrorLogger Logger) + { + Lexer.GobbleandLog("::", "Expected '::' after filtername and before filter params."); + CenterAssetFillFilterParams fparams = new CenterAssetFillFilterParams(); + // parse params + + Lexer.GobbleandLog("asset", "Expected 'asset' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + Lexer.GobbleandLog("(", "Expecting '(' to enclose asset name"); + string pname = Lexer.ConsumeUntil(")", "Expecting paramater: asset"); + Lexer.GobbleandLog(")", "Expecting ')' to enclose asset name"); + fparams.AssetPath = assetids.ToString(); + assetstoresolve[assetids] = pname; + assetids++; + + Lexer.GobbleandLog(";", "Expecting ';' at end of filter"); + + Filters.Add((ImageFilter.CenterAssetFill, fparams)); + + } + + private void CompileFilterCommand_uniformstetch(Lexer Lexer, XenonErrorLogger Logger) + { + Lexer.GobbleandLog("::", "Expected '::' after filtername and before filter params."); + UniformStretchFilterParams fparams = new UniformStretchFilterParams(); + // parse params + + Lexer.GobbleandLog("width", "Expected 'width' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + string pwidth = Lexer.ConsumeUntil(","); + Lexer.GobbleandLog(",", "Expecting ',' before height parameter"); + int.TryParse(pwidth, out int width); + fparams.Width = width; + + Lexer.GobbleandLog("height", "Expected 'height' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + string pheight = Lexer.ConsumeUntil(","); + Lexer.GobbleandLog(",", "Expecting ',' before fill parameter"); + int.TryParse(pheight, out int height); + fparams.Height = height; + + Lexer.GobbleandLog("fill", "Expecting 'fill' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + Lexer.GobbleandLog("(", "Expecting open '('"); + string pcolorfill = Lexer.ConsumeUntil(")", "Expecting parameter: fill (r,g,b) e.g. (0, 132, 39)"); + Lexer.GobbleandLog(")", "Expecting closing ')'"); + Lexer.GobbleandLog(",", "Expecting , before next parameter"); + fparams.Fill = GraphicsHelper.ColorFromRGB(pcolorfill); + + Lexer.GobbleandLog("kfill", "Expecting 'kfill' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + Lexer.GobbleandLog("(", "Expecting open '('"); + string pkeyfill = Lexer.ConsumeUntil(")", "Expecting parameter: kfill (r,g,b) e.g. (0, 132, 39)"); + Lexer.GobbleandLog(")", "Expecting closing ')'"); + fparams.KFill = GraphicsHelper.ColorFromRGB(pkeyfill); + + + Lexer.GobbleandLog(";", "Expecting ';' at end of filter"); + + Filters.Add((ImageFilter.UniformStretch, fparams)); + } + + private void CompileFilterCommand_centeronbackground(Lexer Lexer, XenonErrorLogger Logger) + { + Lexer.GobbleandLog("::", "Expected '::' after filtername and before filter params."); + CenterOnBackgroundFilterParams fparams = new CenterOnBackgroundFilterParams(); + // parse params + + Lexer.GobbleandLog("width", "Expected 'width' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + string pwidth = Lexer.ConsumeUntil(","); + Lexer.GobbleandLog(",", "Expecting ',' before height parameter"); + int.TryParse(pwidth, out int width); + fparams.Width = width; + + Lexer.GobbleandLog("height", "Expected 'height' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + string pheight = Lexer.ConsumeUntil(","); + Lexer.GobbleandLog(",", "Expecting ',' before fill parameter"); + int.TryParse(pheight, out int height); + fparams.Height = height; + + Lexer.GobbleandLog("fill", "Expecting 'fill' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + Lexer.GobbleandLog("(", "Expecting open '('"); + string pcolorfill = Lexer.ConsumeUntil(")", "Expecting parameter: fill (r,g,b) e.g. (0, 132, 39)"); + Lexer.GobbleandLog(")", "Expecting closing ')'"); + Lexer.GobbleandLog(",", "Expecting , before next parameter"); + fparams.Fill = GraphicsHelper.ColorFromRGB(pcolorfill); + + Lexer.GobbleandLog("kfill", "Expecting 'kfill' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + Lexer.GobbleandLog("(", "Expecting open '('"); + string pkeyfill = Lexer.ConsumeUntil(")", "Expecting parameter: kfill (r,g,b) e.g. (0, 132, 39)"); + Lexer.GobbleandLog(")", "Expecting closing ')'"); + fparams.KFill = GraphicsHelper.ColorFromRGB(pkeyfill); + + + Lexer.GobbleandLog(";", "Expecting ';' at end of filter"); + + Filters.Add((ImageFilter.CenterOnBackground, fparams)); + } + + private void CompileFilterCommand_coloredit(Lexer Lexer, XenonErrorLogger Logger) + { + Lexer.GobbleandLog("::", "Expected '::' after filtername and before filter params."); + ColorEditFilterParams fparams = new ColorEditFilterParams(); + // parse params + + Lexer.GobbleandLog("identifier", "Expecting 'identifier' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + Lexer.GobbleandLog("(", "Expecting open '('"); + string pcolorrgb = Lexer.ConsumeUntil(")", "Expecting parameter: identifier (r,g,b) e.g. (0, 132, 39)"); + Lexer.GobbleandLog(")", "Expecting closing ')'"); + Lexer.GobbleandLog(",", "Expecting , before next parameter"); + fparams.Identifier = GraphicsHelper.ColorFromRGB(pcolorrgb); + + Lexer.GobbleandLog("replace", "Expecting 'replace' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + Lexer.GobbleandLog("(", "Expecting open '('"); + string preplacergb = Lexer.ConsumeUntil(")", "Expecting parameter: replace (r,g,b) e.g. (0, 132, 39)"); + Lexer.GobbleandLog(")", "Expecting closing ')'"); + Lexer.GobbleandLog(",", "Expecting , before next parameter"); + fparams.Replace = GraphicsHelper.ColorFromRGB(preplacergb); + + Lexer.GobbleandLog("exclude", "Expected 'exclude' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + string pexclude = Lexer.ConsumeUntil(",", "Expecting paramater: exclude (True, False)"); + Lexer.GobbleandLog(",", "Expecting ',' before next parameter"); + bool.TryParse(pexclude, out bool exclude); + fparams.IsExcludeMatch = exclude; + + Lexer.GobbleandLog("forkey", "Expected 'exclude' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to start parameter value"); + string pkey = Lexer.ConsumeUntil(",", "Expecting paramater: exclude (True, False)"); + Lexer.GobbleandLog(",", "Expecting ',' before next parameter"); + bool.TryParse(pkey, out bool key); + fparams.ForKey = key; + + Lexer.GobbleandLog("rtol", "Expecting 'rtol' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to starrt parameter value"); + fparams.RTolerance = Convert.ToInt32(Lexer.ConsumeUntil(",", "Expecting tolerance value e.g. 123")); + Lexer.GobbleandLog(",", "Expecting ',' before next parameter"); + Lexer.GobbleWhitespace(); + Lexer.GobbleandLog("gtol", "Expecting 'gtol' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to starrt parameter value"); + fparams.GTolerance = Convert.ToInt32(Lexer.ConsumeUntil(",", "Expecting tolerance value e.g. 123")); + Lexer.GobbleandLog(",", "Expecting ',' before next parameter"); + Lexer.GobbleWhitespace(); + Lexer.GobbleandLog("btol", "Expecting 'btol' parameter"); + Lexer.GobbleandLog("=", "Expecting '=' to starrt parameter value"); + fparams.BTolerance = Convert.ToInt32(Lexer.ConsumeUntil(";", "Expecting tolerance value e.g. 123")); + Lexer.GobbleandLog(";", "Expecting ';' at end of filter"); + + Filters.Add((ImageFilter.ColorEdit, fparams)); + + } public void Generate(Project project, IXenonASTElement _Project) { @@ -171,16 +346,24 @@ public void Generate(Project project, IXenonASTElement _Project) imageslide.Name = "UNNAMED_image"; imageslide.Number = project.NewSlideNumber; imageslide.Lines = new List(); + imageslide.Asset = ""; - string assetpath = ""; - var asset = project.Assets.Find(p => p.Name == AssetName); - if (asset != null) + + // resolve assets used in filters + foreach (var f in Filters) { - assetpath = asset.CurrentPath; + if (f.Type == ImageFilter.CenterAssetFill) + { + int id = int.Parse((f.FParams as CenterAssetFillFilterParams).AssetPath); + var asset = project.Assets.Find(p => p.Name == assetstoresolve[id]); + if (asset != null) + { + (f.FParams as CenterAssetFillFilterParams).AssetPath = asset.CurrentPath; + } + } } imageslide.Format = SlideFormat.FilterImage; - imageslide.Asset = assetpath; imageslide.MediaType = MediaType.Image; // set filter data @@ -193,7 +376,6 @@ public void Generate(Project project, IXenonASTElement _Project) public void GenerateDebug(Project project) { Debug.WriteLine(""); - Debug.WriteLine(AssetName); Debug.WriteLine(""); Debug.WriteLine(Filters); Debug.WriteLine(""); diff --git a/Xenon/Compiler/LanguageKeywords.cs b/Xenon/Compiler/LanguageKeywords.cs index 7658fa46..d5d08aba 100644 --- a/Xenon/Compiler/LanguageKeywords.cs +++ b/Xenon/Compiler/LanguageKeywords.cs @@ -7,6 +7,35 @@ namespace Xenon.Compiler static class LanguageKeywords { + public static List WholeWords = new List() + { + "solidcolorcanvas", + "crop", + "centerassetfill", + "asset", + "width", + "height", + "color", + "kcolor", + "bound", + "Top", + "Left", + "Bottom", + "Right", + "True", + "False", + "icolor", + "rtol", + "gtol", + "btol", + "uniformstretch", + "fill", + "kfill", + "centeronbackground", + "coloredit", + "forkey", + }; + public static Dictionary Commands = new Dictionary() { [LanguageKeywordCommand.Script_LiturgyOff] = "liturgyoff", diff --git a/Xenon/Compiler/Lexer.cs b/Xenon/Compiler/Lexer.cs index 8aec2640..f144d941 100644 --- a/Xenon/Compiler/Lexer.cs +++ b/Xenon/Compiler/Lexer.cs @@ -75,6 +75,7 @@ public Lexer(ILexerLogger Logger) SplitWords = new List(); SplitWords.AddRange(LanguageKeywords.Commands.Values); + SplitWords.AddRange(LanguageKeywords.WholeWords); List Seperators = new List() { "\r\n", "//", @@ -97,6 +98,7 @@ public Lexer(ILexerLogger Logger) "=", }; SplitWords.AddRange(Seperators); + SplitWords = SplitWords.OrderByDescending(s => s.Length).ToList(); } private List SplitAndKeep(string text, List splitwords) diff --git a/Xenon/Renderer/ImageFilters/ImageFilter.cs b/Xenon/Renderer/ImageFilters/ImageFilter.cs index 5455c847..9bbdcb77 100644 --- a/Xenon/Renderer/ImageFilters/ImageFilter.cs +++ b/Xenon/Renderer/ImageFilters/ImageFilter.cs @@ -6,8 +6,11 @@ namespace Xenon.Renderer.ImageFilters { enum ImageFilter { + CenterAssetFill, SolidColorCanvas, Crop, - Resize, + UniformStretch, + CenterOnBackground, + ColorEdit, } } diff --git a/Xenon/Renderer/ImageFilters/ImageFilterParams.cs b/Xenon/Renderer/ImageFilters/ImageFilterParams.cs index fbef0b49..442d99a7 100644 --- a/Xenon/Renderer/ImageFilters/ImageFilterParams.cs +++ b/Xenon/Renderer/ImageFilters/ImageFilterParams.cs @@ -9,7 +9,15 @@ class ImageFilterParams { } - class SolidColorCanvasFilterParams: ImageFilterParams + class CenterAssetFillFilterParams : ImageFilterParams + { + /// + /// Path to image asset. + /// + public string AssetPath { get; set; } + } + + class SolidColorCanvasFilterParams : ImageFilterParams { /// /// Width of canvas. @@ -66,6 +74,39 @@ public enum CropBound public int BTolerance { get; set; } } + class ColorEditFilterParams : ImageFilterParams + { + /// + /// Color to match for applying color edit to. + /// + public Color Identifier { get; set; } + /// + /// Color to replace editied pixels with. + /// + public Color Replace { get; set; } + /// + /// If True will instead replace all pixels that don't match identifier within tolerance. + /// + public bool IsExcludeMatch { get; set; } + /// + /// True if should be applied on key instead of slide. + /// + public bool ForKey { get; set; } + /// + /// R tolerance. + /// + public int RTolerance { get; set; } + /// + /// G tolerance. + /// + public int GTolerance { get; set; } + /// + /// B tolerance. + /// + public int BTolerance { get; set; } + + } + class UniformStretchFilterParams : ImageFilterParams { /// @@ -76,6 +117,35 @@ class UniformStretchFilterParams : ImageFilterParams /// Target Height. /// public int Height { get; set; } + /// + /// Color to fill extra space. + /// + public Color Fill { get; set; } + /// + /// Color to fill key extra space. + /// + public Color KFill { get; set; } + } + + class CenterOnBackgroundFilterParams : ImageFilterParams + { + /// + /// Background width. + /// + public int Width { get; set; } + /// + /// Background height. + /// + public int Height { get; set; } + /// + /// Background color. + /// + public Color Fill { get; set; } + /// + /// Background key color. + /// + public Color KFill { get; set; } } + } diff --git a/Xenon/Renderer/ImageFilters/ImageFilterRenderer.cs b/Xenon/Renderer/ImageFilters/ImageFilterRenderer.cs index 6722d9f6..f8a39672 100644 --- a/Xenon/Renderer/ImageFilters/ImageFilterRenderer.cs +++ b/Xenon/Renderer/ImageFilters/ImageFilterRenderer.cs @@ -55,10 +55,16 @@ public RenderedSlide RenderImageSlide(Slide slide, List me { case ImageFilter.SolidColorCanvas: return ImageFilters.SolidColorCanvas(inb, inkb, ifparams as SolidColorCanvasFilterParams); + case ImageFilter.CenterAssetFill: + return ImageFilters.CenterFillAsset(inb, inkb, ifparams as CenterAssetFillFilterParams); case ImageFilter.Crop: - break; - case ImageFilter.Resize: - break; + return ImageFilters.Crop(inb, inkb, ifparams as CropFilterParams); + case ImageFilter.UniformStretch: + return ImageFilters.UniformStretch(inb, inkb, ifparams as UniformStretchFilterParams); + case ImageFilter.CenterOnBackground: + return ImageFilters.CenterOnBackground(inb, inkb, ifparams as CenterOnBackgroundFilterParams); + case ImageFilter.ColorEdit: + return ImageFilters.ColorEdit(inb, inkb, ifparams as ColorEditFilterParams); } return (inb, inkb); } diff --git a/Xenon/Renderer/ImageFilters/ImageFilters.cs b/Xenon/Renderer/ImageFilters/ImageFilters.cs index 77aaea28..455549dc 100644 --- a/Xenon/Renderer/ImageFilters/ImageFilters.cs +++ b/Xenon/Renderer/ImageFilters/ImageFilters.cs @@ -9,6 +9,281 @@ namespace Xenon.Renderer.ImageFilters static class ImageFilters { + /// + /// Searches the image in the provided direction for the first pixel that matches within tolerance. + /// + /// Source Image. + /// Direction to search from. 1 = Top, 2 = Bottom, 3 = Left, 4 = Right + /// Color to match + /// Red tolerance + /// Green tolerance + /// Blue tolerance + /// If True will find first pixel that does not match within tolerances. + /// The X and Y coordinates of the matched pixel + private static (int x, int y) SearchBitmapForColorWithTolerance(this Bitmap source, int direction, Color match, int rtolerance, int gtolerance, int btolerance, bool isexclude) + { + if (direction == 1) + { + for (int y = 0; y < source.Height; y++) + { + for (int x = 0; x < source.Width; x++) + { + Color pix = source.GetPixel(x, y); + if (Math.Abs(pix.R - match.R) <= rtolerance && Math.Abs(pix.G - match.G) <= gtolerance && Math.Abs(pix.B - match.B) <= btolerance) + { + if (!isexclude) + { + return (x, y); + } + } + else if (isexclude) + { + return (x, y); + } + } + } + } + else if (direction == 2) + { + for (int y = source.Height - 1; y >= 0; y--) + { + for (int x = source.Width - 1; x >= 0; x--) + { + Color pix = source.GetPixel(x, y); + if (Math.Abs(pix.R - match.R) <= rtolerance && Math.Abs(pix.G - match.G) <= gtolerance && Math.Abs(pix.B - match.B) <= btolerance) + { + if (!isexclude) + { + return (x, y); + } + } + else if (isexclude) + { + return (x, y); + } + + } + } + } + else if (direction == 3) + { + for (int x = 0; x < source.Width; x++) + { + for (int y = 0; y < source.Height; y++) + { + Color pix = source.GetPixel(x, y); + if (Math.Abs(pix.R - match.R) <= rtolerance && Math.Abs(pix.G - match.G) <= gtolerance && Math.Abs(pix.B - match.B) <= btolerance) + { + if (!isexclude) + { + return (x, y); + } + } + else if (isexclude) + { + return (x, y); + } + + } + } + } + else if (direction == 4) + { + for (int x = source.Width - 1; x >= 0; x--) + { + for (int y = source.Height - 1; y >= 0; y--) + { + Color pix = source.GetPixel(x, y); + if (Math.Abs(pix.R - match.R) <= rtolerance && Math.Abs(pix.G - match.G) <= gtolerance && Math.Abs(pix.B - match.B) <= btolerance) + { + if (!isexclude) + { + return (x, y); + } + } + else if (isexclude) + { + return (x, y); + } + + } + } + } + return (0, 0); + } + + public static (Bitmap b, Bitmap k) Crop(Bitmap inb, Bitmap inkb, CropFilterParams fparams) + { + // get bounds to crop + (int x, int y) bounds = inb.SearchBitmapForColorWithTolerance((int)fparams.Bound + 1, fparams.Identifier, fparams.RTolerance, fparams.GTolerance, fparams.BTolerance, fparams.IsExcludeMatch); + + int top = 0; + int bottom = 0; + int left = 0; + int right = 0; + int width = 0; + int height = 0; + + if (fparams.Bound == CropFilterParams.CropBound.Top) + { + top = bounds.y; + left = 0; + right = inb.Width; + bottom = inb.Height; + width = inb.Width; + height = inb.Height - top; + } + else if (fparams.Bound == CropFilterParams.CropBound.Bottom) + { + top = 0; + left = 0; + right = inb.Width; + bottom = bounds.y; + width = inb.Width; + height = bottom; + } + else if (fparams.Bound == CropFilterParams.CropBound.Left) + { + top = 0; + left = bounds.x; + right = inb.Width; + bottom = inb.Height; + width = inb.Width - left; + height = inb.Height; + } + else if (fparams.Bound == CropFilterParams.CropBound.Right) + { + top = 0; + left = 0; + right = bounds.x; + bottom = inb.Height; + width = right; + height = inb.Height; + } + + Bitmap _b = new Bitmap(width, height); + Bitmap _k = new Bitmap(width, height); + Graphics gfx = Graphics.FromImage(_b); + Graphics kgfx = Graphics.FromImage(_k); + + gfx.DrawImage(inb, 0, 0, new Rectangle(left, top, right, bottom), GraphicsUnit.Pixel); + kgfx.DrawImage(inkb, 0, 0, new Rectangle(left, top, right, bottom), GraphicsUnit.Pixel); + + return (_b, _k); + } + + public static (Bitmap b, Bitmap k) ColorEdit(Bitmap inb, Bitmap inkb, ColorEditFilterParams fparams) + { + Bitmap _b = new Bitmap(inb); + Bitmap _k = new Bitmap(inkb); + + Bitmap source = _b; + if (fparams.ForKey) + { + source = _k; + } + + for (int y = 0; y < _b.Height; y++) + { + for (int x = 0; x < _b.Width; x++) + { + Color pix = source.GetPixel(x, y); + if (Math.Abs(pix.R - fparams.Identifier.R) <= fparams.RTolerance && Math.Abs(pix.G - fparams.Identifier.G) <= fparams.GTolerance && Math.Abs(pix.B - fparams.Identifier.B) <= fparams.BTolerance) + { + if (!fparams.IsExcludeMatch) + { + source.SetPixel(x, y, fparams.Replace); + } + } + else if (fparams.IsExcludeMatch) + { + source.SetPixel(x, y, fparams.Replace); + } + } + } + + return (_b, _k); + } + + public static (Bitmap b, Bitmap k) CenterOnBackground(Bitmap inb, Bitmap inkb, CenterOnBackgroundFilterParams fparams) + { + Bitmap _b = new Bitmap(Math.Max(fparams.Width, inb.Width), Math.Max(fparams.Height, inb.Height)); + Bitmap _k = new Bitmap(Math.Max(fparams.Width, inb.Width), Math.Max(fparams.Height, inb.Height)); + Graphics gfx = Graphics.FromImage(_b); + Graphics kgfx = Graphics.FromImage(_k); + + int xoff = inb.Width < _b.Width ? (_b.Width - inb.Width) / 2 : 0; + int yoff = inb.Height < _b.Height ? (_b.Height - inb.Height) / 2 : 0; + + gfx.Clear(fparams.Fill); + kgfx.Clear(fparams.KFill); + + gfx.DrawImage(inb, xoff, yoff); + kgfx.DrawImage(inkb, xoff, yoff); + + return (_b, _k); + } + + public static (Bitmap b, Bitmap k) UniformStretch(Bitmap inb, Bitmap inkb, UniformStretchFilterParams fparams) + { + double xscale = ((double)fparams.Width / inb.Width); + double yscale = ((double)fparams.Height / inb.Height); + double scale = xscale < yscale ? xscale : yscale; + + Bitmap _b = new Bitmap(fparams.Width, fparams.Height); + Bitmap _k = new Bitmap(fparams.Width, fparams.Height); + Graphics gfx = Graphics.FromImage(_b); + Graphics kgfx = Graphics.FromImage(_k); + + gfx.Clear(fparams.Fill); + kgfx.Clear(fparams.KFill); + + Point p = new Point((int)((fparams.Width - (inb.Width * scale)) / 2), (int)((fparams.Height - (inb.Height * scale)) / 2)); + + gfx.DrawImage(inb, new Rectangle(p, new Size((int)(inb.Width * scale), (int)(inb.Height * scale))), new Rectangle(0, 0, inb.Width, inb.Height), GraphicsUnit.Pixel); + kgfx.DrawImage(inkb, new Rectangle(p, new Size((int)(inb.Width * scale), (int)(inb.Height * scale))), new Rectangle(0, 0, inb.Width, inb.Height), GraphicsUnit.Pixel); + + return (_b, _k); + } + + public static (Bitmap b, Bitmap k) CenterFillAsset(Bitmap inb, Bitmap inkb, CenterAssetFillFilterParams fparams) + { + Bitmap sourceimage; + try + { + sourceimage = new Bitmap(fparams.AssetPath); + } + catch (Exception) + { + throw new Exception($"Unable to load image <${fparams.AssetPath}> in filter"); + } + + Bitmap _b = new Bitmap(inb); + Bitmap _k = new Bitmap(inkb); + + Graphics gfx = Graphics.FromImage(_b); + + if (sourceimage.Height > _b.Height || sourceimage.Width > _b.Width) + { + // overwrite with image + // apply default white key + + _b = new Bitmap(sourceimage); + _k = new Bitmap(sourceimage.Width, sourceimage.Height); + + Graphics kgfx = Graphics.FromImage(_k); + kgfx.Clear(Color.White); + } + else + { + int xoff = (_b.Width - sourceimage.Width) / 2; + int yoff = (_b.Height - sourceimage.Height) / 2; + gfx.DrawImageUnscaled(sourceimage, xoff, yoff); + } + + return (_b, _k); + } + public static (Bitmap b, Bitmap k) SolidColorCanvas(Bitmap inb, Bitmap inkb, SolidColorCanvasFilterParams fparams) { Bitmap _b = new Bitmap(fparams.Width, fparams.Height);