using System; using System.IO; using System.Text; using System.Collections; using System.Windows.Forms; using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; using System.Globalization; ////////////////////////////////////////////////////////////////////////////// public class FastBitmap : IDisposable, ICloneable { internal Bitmap _bitmap; private BitmapData _bitmapData; public FastBitmap(Int32 width, Int32 height, PixelFormat fmt) { _bitmap = new Bitmap(width, height, fmt); } public FastBitmap(String filename) { _bitmap = new Bitmap(filename); } ~FastBitmap() { Dispose(false); } public void Dispose() { GC.SuppressFinalize(this); Dispose(true); } protected virtual void Dispose(Boolean disposing) { Unlock(); if (disposing) { _bitmap.Dispose(); } } private FastBitmap() { } public Object Clone() { FastBitmap clone = new FastBitmap(); clone._bitmap = (Bitmap)_bitmap.Clone(); return clone; } public Int32 Width { get { return _bitmap.Width; } } public Int32 Height { get { return _bitmap.Height; } } public void Lock() { _bitmapData = _bitmap.LockBits( new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadWrite, _bitmap.PixelFormat ); } unsafe public Color GetPixel(Int32 x, Int32 y) { if (_bitmapData.PixelFormat == PixelFormat.Format32bppArgb) { Byte* b = (Byte*)_bitmapData.Scan0 + (y * _bitmapData.Stride) + (x * 4); return Color.FromArgb(*(b + 3), *(b + 2), *(b + 1), *b); } if (_bitmapData.PixelFormat == PixelFormat.Format24bppRgb) { Byte* b = (Byte*)_bitmapData.Scan0 + (y * _bitmapData.Stride) + (x * 3); return Color.FromArgb(*(b + 2), *(b + 1), *b); } return Color.Empty; } unsafe public void SetPixel(Int32 x, Int32 y, Color c) { if (_bitmapData.PixelFormat == PixelFormat.Format32bppArgb) { Byte* b = (Byte*)_bitmapData.Scan0 + (y * _bitmapData.Stride) + (x * 4); *b = c.B; *(b + 1) = c.G; *(b + 2) = c.R; *(b + 3) = c.A; } if (_bitmapData.PixelFormat == PixelFormat.Format24bppRgb) { Byte* b = (Byte*)_bitmapData.Scan0 + (y * _bitmapData.Stride) + (x * 3); *b = c.B; *(b + 1) = c.G; *(b + 2) = c.R; } } public void CopyTo(FastBitmap bitmap, Int32 destx, Int32 desty, Int32 srcx, Int32 srcy, Int32 width, Int32 height) { try { Lock(); bitmap.Lock(); for (Int32 y = 0; y < height; y++) { for (Int32 x = 0; x < width; x++) { Color c = GetPixel(srcx + x, srcy + y); bitmap.SetPixel(destx + x, desty + y, c); } } } finally { Unlock(); bitmap.Unlock(); } } public Byte GetIntensity(Int32 x, Int32 y) { Color c = GetPixel(x, y); return (Byte)((c.R * 0.30 + c.G * 0.59 + c.B * 0.11) + 0.5); } public void Unlock() { if (_bitmapData != null) { _bitmap.UnlockBits(_bitmapData); _bitmapData = null; } } public void Save(String filename, ImageFormat format) { _bitmap.Save(filename, format); } public void Save(String filename) { _bitmap.Save(filename); } } ////////////////////////////////////////////////////////////////////////////// class Layer : ICloneable { internal FastBitmap _bitmap; private FastBitmap _mask; public Double _opacity; private Int32 _offsetx, _offsety; public Layer(Int32 width, Int32 height) { _bitmap = new FastBitmap(width, height, PixelFormat.Format32bppArgb); Clear(Color.Transparent); _opacity = 1.0; } public Layer(FastBitmap bitmap) { _bitmap = bitmap; _opacity = 1.0; } public Double Opacity { get { return _opacity; } set { _opacity = value; } } public FastBitmap Bitmap { get { return _bitmap; } } public FastBitmap Mask { get { return _mask; } set { _mask = value; } } public Int32 OffsetX { get { return _offsetx; } set { _offsetx = value; } } public Int32 OffsetY { get { return _offsety; } set { _offsety = value; } } public Object Clone() { Layer clone = new Layer(_bitmap.Width, _bitmap.Height); clone._bitmap = (FastBitmap)_bitmap.Clone(); return clone; } public void Clear(Color c) { _bitmap.Lock(); for (Int32 y = 0; y < _bitmap.Height; y++) for (Int32 x = 0; x < _bitmap.Width; x++) _bitmap.SetPixel(x, y, c); _bitmap.Unlock(); } public void DrawText(Int32 x, Int32 y, String text, Font font, Brush brush) { Graphics g = Graphics.FromImage(_bitmap._bitmap); g.DrawString(text, font, brush, x, y, StringFormat.GenericTypographic); g.Dispose(); } public void FillRectangle(Int32 x, Int32 y, Int32 width, Int32 height, Brush brush) { Graphics g = Graphics.FromImage(_bitmap._bitmap); g.FillRectangle(brush, x, y, width, height); g.Dispose(); } public Color GetPixel(Int32 x, Int32 y) { return _bitmap.GetPixel(x, y); } public void Invert() { _bitmap.Lock(); for (Int32 y = 0; y < _bitmap.Height; y++) { for (Int32 x = 0; x < _bitmap.Width; x++) { Color c = _bitmap.GetPixel(x, y); _bitmap.SetPixel(x, y, Color.FromArgb(c.A, 255 - c.R, 255 - c.G, 255 - c.B)); } } _bitmap.Unlock(); } private Single Gauss(Single x, Single middle, Single width) { if (width == 0) return 1F; Double t = -(1.0 / width) * ((middle - x) * (middle - x)); return (Single)Math.Pow(/*Math.E*/1.5, t); } public void Blur(Int32 horz, Int32 vert) { Single weightsum; Single[] weights; FastBitmap t = (FastBitmap)_bitmap.Clone(); _bitmap.Lock(); t.Lock(); // horizontal blur weights = new Single[horz * 2 + 1]; for (Int32 i = 0; i < horz * 2 + 1; i++) { Single y = Gauss(-horz + i, 0, horz); weights[i] = y; } for (Int32 row = 0; row < _bitmap.Height; row++) { for (Int32 col = 0; col < _bitmap.Width; col++) { Double r = 0; Double g = 0; Double b = 0; Double a = 0; weightsum = 0; for (Int32 i = 0; i < horz * 2 + 1; i++) { Int32 x = col - horz + i; if (x < 0) { i += -x; x = 0; } if (x > _bitmap.Width - 1) break; Color c = _bitmap.GetPixel(x, row); r += c.R * weights[i] / 255.0 * c.A; g += c.G * weights[i] / 255.0 * c.A; b += c.B * weights[i] / 255.0 * c.A; a += c.A * weights[i]; weightsum += weights[i]; } r /= weightsum; g /= weightsum; b /= weightsum; a /= weightsum; Byte br = (Byte)Math.Round(r); Byte bg = (Byte)Math.Round(g); Byte bb = (Byte)Math.Round(b); Byte ba = (Byte)Math.Round(a); if (br > 255) br = 255; if (bg > 255) bg = 255; if (bb > 255) bb = 255; if (ba > 255) ba = 255; t.SetPixel(col, row, Color.FromArgb(ba, br, bg, bb)); } } // vertical blur weights = new Single[vert * 2 + 1]; for (Int32 i = 0; i < vert * 2 + 1; i++) { Single y = Gauss(-vert + i, 0, vert); weights[i] = y; } for (Int32 col = 0; col < _bitmap.Width; col++) { for (Int32 row = 0; row < _bitmap.Height; row++) { Double r = 0; Double g = 0; Double b = 0; Double a = 0; weightsum = 0; for (Int32 i = 0; i < vert * 2 + 1; i++) { Int32 y = row - vert + i; if (y < 0) { i += -y; y = 0; } if (y > _bitmap.Height - 1) break; Color c = t.GetPixel(col, y); r += c.R * weights[i] / 255.0 * c.A; g += c.G * weights[i] / 255.0 * c.A; b += c.B * weights[i] / 255.0 * c.A; a += c.A * weights[i]; weightsum += weights[i]; } r /= weightsum; g /= weightsum; b /= weightsum; a /= weightsum; Byte br = (Byte)Math.Round(r); Byte bg = (Byte)Math.Round(g); Byte bb = (Byte)Math.Round(b); Byte ba = (Byte)Math.Round(a); if (br > 255) br = 255; if (bg > 255) bg = 255; if (bb > 255) bb = 255; if (ba > 255) ba = 255; _bitmap.SetPixel(col, row, Color.FromArgb(ba, br, bg, bb)); } } t.Dispose(); // will unlock _bitmap.Unlock(); } private Byte GetBumpMapPixel(FastBitmap bmp, Int32 x, Int32 y) { // create a single number from R, G and B values at point (x, y) // if point (x, y) lays outside the bitmap, GetBumpMapPixel() // returns the closest pixel within the bitmap if (x < 0) x = 0; if (x > _bitmap.Width - 1) x = _bitmap.Width - 1; if (y < 0) y = 0; if (y > _bitmap.Height - 1) y = _bitmap.Height - 1; return bmp.GetIntensity(x, y); } public void BumpMap(Layer bumpmap, Int32 azimuth, Int32 elevation, Int32 bevelwidth, Boolean lightzalways1) { bumpmap._bitmap.Lock(); _bitmap.Lock(); for (Int32 row = 0; row < _bitmap.Height; row++) { for (Int32 col = 0; col < _bitmap.Width; col++) { // calculate normal for point (col, row) // this is an approximation of the derivative // I personally haven't figured out why it's // the way it is // normal_z is constant, I think this means // the longer the vector is, the more it lays // in the xy plane Byte[] x = new Byte[6]; x[0] = GetBumpMapPixel(bumpmap._bitmap, col - 1, row - 1); x[1] = GetBumpMapPixel(bumpmap._bitmap, col - 1, row - 0); x[2] = GetBumpMapPixel(bumpmap._bitmap, col - 1, row + 1); x[3] = GetBumpMapPixel(bumpmap._bitmap, col + 1, row - 1); x[4] = GetBumpMapPixel(bumpmap._bitmap, col + 1, row - 0); x[5] = GetBumpMapPixel(bumpmap._bitmap, col + 1, row + 1); Single normal_x = x[0] + x[1] + x[2] - x[3] - x[4] - x[5]; x[0] = GetBumpMapPixel(bumpmap._bitmap, col - 1, row + 1); x[1] = GetBumpMapPixel(bumpmap._bitmap, col - 0, row + 1); x[2] = GetBumpMapPixel(bumpmap._bitmap, col + 1, row + 1); x[3] = GetBumpMapPixel(bumpmap._bitmap, col - 1, row - 1); x[4] = GetBumpMapPixel(bumpmap._bitmap, col - 0, row - 1); x[5] = GetBumpMapPixel(bumpmap._bitmap, col + 1, row - 1); Single normal_y = x[0] + x[1] + x[2] - x[3] - x[4] - x[5]; Single normal_z = (6F * 255F) / bevelwidth; Single length = (Single)Math.Sqrt( normal_x * normal_x + normal_y * normal_y + normal_z * normal_z); if (length != 0) { normal_x /= length; normal_y /= length; normal_z /= length; } // convert to radians Double azimuth_rad = azimuth / 180.0 * Math.PI; Double elevation_rad = elevation / 180.0 * Math.PI; // light vector -- this is the location of the light // source but it also serves as the direction with // origin <0, 0, 0> // the formulas to calculate x, y and z are those to // rotate a point in 3D space // if lightzalways1 is true, light_z is set to 1 // because we want full color intensity for that pixel; // if we set light_z to (Single)Math.Sin(elevation_rad), // which is the correct way to calculate light_z, the // color is more dark, but when we ignore light_z, the // light source is straight above the pixel and // therefore sin(elevation_rad) is always 1 Single light_x = (Single)(Math.Cos(azimuth_rad) * Math.Cos(elevation_rad)); Single light_y = (Single)(Math.Sin(azimuth_rad) * Math.Cos(elevation_rad)); Single light_z = 1F; if (!lightzalways1) light_z = (Single)Math.Sin(elevation_rad); // the normal and light vector are unit vectors, so // taking the dot product of these two yields the // cosine of the angle between them Single cos_light_normal = 0; cos_light_normal += normal_x * light_x; cos_light_normal += normal_y * light_y; cos_light_normal += normal_z * light_z; // get pixel (col, row) of this layer, calculate color // and set pixel back with new color Color c = _bitmap.GetPixel(col, row); Single r = c.R; Single g = c.G; Single b = c.B; r *= cos_light_normal; g *= cos_light_normal; b *= cos_light_normal; Byte red = (Byte)Math.Min(Math.Round(r), 255); Byte green = (Byte)Math.Min(Math.Round(g), 255); Byte blue = (Byte)Math.Min(Math.Round(b), 255); _bitmap.SetPixel(col, row, Color.FromArgb(red, green, blue)); } } _bitmap.Unlock(); bumpmap._bitmap.Unlock(); } } ////////////////////////////////////////////////////////////////////////////// class Layers { LayeredImage _image; ArrayList _layers = new ArrayList(); public Layers(LayeredImage image) { _image = image; } public Int32 Count { get { return _layers.Count; } } public Layer Add() { Layer layer = new Layer(_image.Width, _image.Height); _layers.Add(layer); return layer; } public Layer Add(FastBitmap bitmap) { Layer layer = new Layer(bitmap); _layers.Add(layer); return layer; } public Layer Add(Int32 width, Int32 height) { Layer layer = new Layer(width, height); _layers.Add(layer); return layer; } public Layer Copy(Layer layer) { Layer copy = (Layer)layer.Clone(); _layers.Add(copy); return copy; } public Layer this[Int32 i] { get { return (Layer)_layers[i]; } } } ////////////////////////////////////////////////////////////////////////////// class LayeredImage { Int32 _width, _height; Bitmap _checkerboard; Layers _layers; public LayeredImage(Int32 width, Int32 height) { _width = width; _height = height; _layers = new Layers(this); // checker board brush _checkerboard = new Bitmap(32, 32, PixelFormat.Format24bppRgb); Color darkgray = Color.FromArgb(102, 102, 102); Color lightgray = Color.FromArgb(153, 153, 153); for (Int32 i = 0; i < 32; i++) { for (Int32 j = 0; j < 32; j++) { if ((i < 16 && j < 16) || (i >= 16 && j >= 16)) _checkerboard.SetPixel(j, i, lightgray); else _checkerboard.SetPixel(j, i, darkgray); } } // background layer Layer bglayer = _layers.Add(); Graphics g = Graphics.FromImage(bglayer._bitmap._bitmap); TextureBrush brush = new TextureBrush(_checkerboard); g.FillRectangle(brush, 0, 0, _width, _height); brush.Dispose(); g.Dispose(); } public Int32 Width { get { return _width; } } public Int32 Height { get { return _height; } } public Layers Layers { get { return _layers; } } internal FastBitmap Flatten() { // create a bitmap for result image FastBitmap final = new FastBitmap(_width, _height, PixelFormat.Format24bppRgb); // lock all bitmaps final.Lock(); for (Int32 i = 0; i < _layers.Count; i++) { Layer l = _layers[i]; l._bitmap.Lock(); if (l.Mask != null) l.Mask.Lock(); } // calculate colors of flattened image // 1. take offsetx, offsety into consideration // 2. calculate alpha of color (alpha, opacity, mask) // 3. mix colors of current layer and layer below for (Int32 y = 0; y < _height; y++) { for (Int32 x = 0; x < _width; x++) { Color c0 = _layers[0]._bitmap.GetPixel(x, y); for (Int32 i = 1; i < _layers.Count; i++) { Layer layer = _layers[i]; Color c1 = Color.Transparent; if (x >= layer.OffsetX && x <= layer.OffsetX + layer._bitmap.Width - 1 && y >= layer.OffsetY && y <= layer.OffsetY + layer._bitmap.Height - 1) { c1 = layer._bitmap.GetPixel(x - layer.OffsetX, y - layer.OffsetY); } if (c1.A == 255 && layer.Opacity == 1.0 && layer.Mask == null) { c0 = c1; } else { Double tr, tg, tb, a; a = c1.A / 255.0 * layer.Opacity; if (layer.Mask != null) { a *= layer.Mask.GetIntensity(x, y) / 255.0; } tr = c1.R * a + c0.R * (1.0 - a); tg = c1.G * a + c0.G * (1.0 - a); tb = c1.B * a + c0.B * (1.0 - a); tr = Math.Round(tr); tg = Math.Round(tg); tb = Math.Round(tb); tr = Math.Min(tr, 255); tg = Math.Min(tg, 255); tb = Math.Min(tb, 255); c0 = Color.FromArgb((Byte)tr, (Byte)tg, (Byte)tb); } } final.SetPixel(x, y, c0); } } // unlock all bitmaps for (Int32 i = 0; i < _layers.Count; i++) { Layer l = _layers[i]; l._bitmap.Unlock(); if (l.Mask != null) l.Mask.Unlock(); } final.Unlock(); return final; } } ////////////////////////////////////////////////////////////////////////////// class Shadow { static void PrintUsage() { Console.WriteLine("Shadow 1.0 for .NET http://home.tiscali.be/zoetrope/"); Console.WriteLine("Syntax is: SHADOW [ options ] inpfile [ outfile ]"); Console.WriteLine(" -s:xx Shadow width (default = 8)"); Console.WriteLine(" -b:xx Border width (default = 0)"); Console.WriteLine(" -m:xx Margin (default = 8)"); Console.WriteLine(" -r:x Shadow direction (default = 0)"); Console.WriteLine(" -t:N Shadow transparency (default = 0.0)"); Console.WriteLine(" -a:xxxxxx Background color (default = FFFFFF)"); Console.WriteLine(" -c:xxxxxx Shadow color (default = 000000)"); Console.WriteLine(" -d:xxxxxx Border color (default = 000000)"); Console.WriteLine(" -n No soft shadow"); } private static Color HexColorToColor(String s) { if (s.Length != 6) return Color.Empty; UInt32 r, g, b; r = g = b = 0; for (Int32 i = 0; i < 6; i++) { Int32 n = "0123456789ABCDEF".IndexOf(Char.ToUpper(s[i])); if (n == -1) return Color.Empty; UInt32 x = (UInt32)n; switch (i) { case 0: r |= x << 4; break; case 1: r |= x; break; case 2: g |= x << 4; break; case 3: g |= x; break; case 4: b |= x << 4; break; case 5: b |= x; break; } } return Color.FromArgb((Byte)r, (Byte)g, (Byte)b); } private static Boolean GetArgParam(String arg, out Int32 num) { num = 0; if (arg.Length < 4) return false; String[] str = arg.Split(new Char[] { ':' }, 2); if (str.Length != 2) return false; try { num = Convert.ToInt32(str[1]); return true; } catch { // catch all exceptions Convert.ToInt32() can throw; // no other exceptions can be thrown return false; } } private static Boolean GetArgParam(String arg, out Double num) { num = 0.0; if (arg.Length < 4) return false; String[] str = arg.Split(new Char[] { ':' }, 2); if (str.Length != 2) return false; try { num = Convert.ToDouble(str[1], CultureInfo.InvariantCulture); return true; } catch { // catch all exceptions Convert.ToInt32() can throw; // no other exceptions can be thrown return false; } } private static Boolean GetArgParam(String arg, out Color col) { col = Color.Empty; if (arg.Length != 9) return false; String[] str = arg.Split(new Char[] { ':' }, 2); if (str.Length != 2) return false; col = HexColorToColor(str[1]); return (col != Color.Empty); } static void Main(String[] args) { if (args.Length == 0) { PrintUsage(); return; } Int32 shadowwidth = 8; Int32 borderwidth = 0; Int32 margin = shadowwidth; Int32 shadowdir = 0; Double shadowtrans = 0.0; Color bkcolor = Color.FromArgb(255, 255, 255); Color shadowcolor = Color.FromArgb(0, 0, 0); Color bordercolor = Color.FromArgb(0, 0, 0); Boolean softshadow = true; String inputfile = ""; String outputfile = ""; for (Int32 i = 0; i < args.Length; i++) { if (args[i].Length == 0) { PrintUsage(); return; } if (args[i][0] == '-') { Boolean b; switch (args[i][1]) { case 's': b = GetArgParam(args[i], out shadowwidth); if (!b || (shadowwidth < 0 || shadowwidth > 99)) { Console.WriteLine("Error: Incorrect command line option: -s"); return; } break; case 'b': b = GetArgParam(args[i], out borderwidth); if (!b || (borderwidth < 0 || borderwidth > 99)) { Console.WriteLine("Error: Incorrect command line option: -b"); return; } break; case 'm': b = GetArgParam(args[i], out margin); if (!b || (margin < 0 || margin > 99)) { Console.WriteLine("Error: Incorrect command line option: -b"); return; } break; case 'r': b = GetArgParam(args[i], out shadowdir); if (!b || (shadowdir < 0 || shadowdir > 3)) { Console.WriteLine("Error: Incorrect command line option: -r"); return; } break; case 't': b = GetArgParam(args[i], out shadowtrans); if (!b || (shadowtrans < 0.0 || shadowtrans > 1.0)) { Console.WriteLine("Error: Incorrect command line option: -t"); return; } break; case 'a': b = GetArgParam(args[i], out bkcolor); if (!b) { Console.WriteLine("Error: Incorrect command line option: -a"); return; } break; case 'c': b = GetArgParam(args[i], out shadowcolor); if (!b) { Console.WriteLine("Error: Incorrect command line option: -c"); return; } break; case 'd': b = GetArgParam(args[i], out bordercolor); if (!b) { Console.WriteLine("Error: Incorrect command line option: -d"); return; } break; case 'n': softshadow = false; break; default: Console.WriteLine("Fatal: Illegal option: {0}", args[i]); return; } } else { // must be a file name if (inputfile == "") inputfile = args[i]; else if (outputfile == "") outputfile = args[i]; // ignore other file names } } if (inputfile == "") { Console.WriteLine("Error: No file names given"); return; } FastBitmap tmp, bmp; // try-catch doesn't work like it should if (File.Exists(inputfile)) { tmp = new FastBitmap(inputfile); } else { Console.WriteLine("Error: Could not find file '{0}'", inputfile); return; } bmp = new FastBitmap(tmp.Width + borderwidth * 2, tmp.Height + borderwidth * 2, PixelFormat.Format32bppArgb); // add border if necessary if (borderwidth > 0) { SolidBrush br = new SolidBrush(bordercolor); Graphics g = Graphics.FromImage(bmp._bitmap); g.FillRectangle(br, 0, 0, borderwidth * 2 + tmp.Width, borderwidth * 2 + tmp.Height); g.Dispose(); br.Dispose(); } tmp.CopyTo(bmp, borderwidth, borderwidth, 0, 0, tmp.Width, tmp.Height); tmp.Dispose(); // create image Int32 width = bmp.Width + shadowwidth + margin * 2; Int32 height = bmp.Height + shadowwidth + margin * 2; LayeredImage image = new LayeredImage(width, height); Int32 shadowx = 0, shadowy = 0, imgx = 0, imgy = 0; if (softshadow) { switch (shadowdir) { case 0: shadowx = margin - shadowwidth / 2; shadowy = margin - shadowwidth / 2; imgx = margin; imgy = margin; break; case 1: shadowx = margin + shadowwidth - 3 * (shadowwidth / 2); shadowy = margin - shadowwidth / 2; imgx = margin + shadowwidth; imgy = margin; break; case 2: shadowx = margin + shadowwidth - 3 * (shadowwidth / 2); shadowy = margin + shadowwidth - 3 * (shadowwidth / 2); imgx = margin + shadowwidth; imgy = margin + shadowwidth; break; case 3: shadowx = margin - shadowwidth / 2; shadowy = margin + shadowwidth - 3 * (shadowwidth / 2); imgx = margin; imgy = margin + shadowwidth; break; } } else { switch (shadowdir) { case 0: shadowx = margin; shadowy = margin; imgx = margin; imgy = margin; break; case 1: shadowx = margin - shadowwidth; shadowy = margin; imgx = margin + shadowwidth; imgy = margin; break; case 2: shadowx = margin - shadowwidth; shadowy = margin - shadowwidth; imgx = margin + shadowwidth; imgy = margin + shadowwidth; break; case 3: shadowx = margin; shadowy = margin - shadowwidth; imgx = margin; imgy = margin + shadowwidth; break; } } // background Layer bg = image.Layers.Add(); bg.Clear(bkcolor); // shadow -- layer must be larger because of blur Layer shadow = image.Layers.Add(width + shadowwidth, height + shadowwidth); SolidBrush brush = new SolidBrush(shadowcolor); shadow.FillRectangle(shadowwidth, shadowwidth, bmp.Width, bmp.Height, brush); if (softshadow) shadow.Blur(shadowwidth, shadowwidth); brush.Dispose(); shadow.OffsetX = shadowx; shadow.OffsetY = shadowy; shadow.Opacity = 1.0 - shadowtrans; // image Layer img = image.Layers.Add(bmp); img.OffsetX = imgx; img.OffsetY = imgy; // result FastBitmap result = image.Flatten(); // save String filename = outputfile != "" ? outputfile : inputfile; String ext = Path.GetExtension(filename); if (ext == "") ext = ".bmp"; ext = ext.ToLower(); ImageFormat imgf = ImageFormat.Bmp; switch (ext) { case ".bmp": ext = ".bmp"; imgf = ImageFormat.Bmp; break; case ".jpg": ext = ".jpg"; imgf = ImageFormat.Jpeg; break; case ".jpeg": ext = ".jpeg"; imgf = ImageFormat.Jpeg; break; case ".png": ext = ".png"; imgf = ImageFormat.Png; break; case ".gif": ext = ".gif"; imgf = ImageFormat.Gif; break; default: ext = ".bmp"; imgf = ImageFormat.Bmp; break; } filename = Path.GetFileNameWithoutExtension(filename); result.Save(filename + ext, imgf); } }