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