Exemple #1
0
        static void Main(string[] args)
        {
            for (int i = 0; i < args.Length; i++)
            {
                switch (args[i])
                {
                    case "/proc":
                        if (i + 1 == args.Length)
                        {
                            Console.WriteLine("Use /proc <N> to specify number of processors");
                            return;
                        }

                        int numProcs;

                        if (Int32.TryParse(args[i + 1], out numProcs))
                        {
                            // only increment i if successful b/c we're going to continue the run
                            // with the default # of processors and don't want to automatically 
                            // eat the next parameter.
                            ++i;
                            Processor.LogicalCpuCount = numProcs;
                        }
                        else
                        {
                            Console.WriteLine("You must specify a integer for /proc <N>, continuing with default");
                        }
                        break;

                    case "/image":
                        if (i + 1 == args.Length)
                        {
                            Console.WriteLine("Use /image <filename> to specify a file to perform benchmark with");
                            return;
                        }

                        ++i;
                        benchmarkImageName = args[i];

                        if (!System.IO.File.Exists(benchmarkImageName))
                        {
                            Console.WriteLine("Specified image doesn't exist");
                            return;
                        }
                        break;

                    case "/tsv":
                        useTsvOutput = true;
                        break;

                    case "/?":
                        PrintHelp();
                        return;

                    default:
                        break;
                }
            }

            //Processor.LogicalCpuCount = 1;
            Console.WriteLine("PdnBench v" + PdnInfo.GetVersion());
            Console.WriteLine("Running in " + (8 * Marshal.SizeOf(typeof(IntPtr))) + "-bit mode on Windows " +
                Environment.OSVersion.Version.ToString() + " " +
                OS.Revision + (OS.Revision.Length > 0 ? " " : string.Empty) +
                OS.Type + " " +
                Processor.NativeArchitecture.ToString().ToLower());

            Console.WriteLine("Processor: " + Processor.LogicalCpuCount + "x \"" + Processor.CpuName + "\" @ ~" + GetCpuSpeed() + " MHz");
            Console.WriteLine("Memory: " + ((Memory.TotalPhysicalBytes / 1024) / 1024) + " MB");
            Console.WriteLine();

            Console.WriteLine("Using " + Processor.LogicalCpuCount + " threads.");

            ArrayList benchmarks = new ArrayList();

            Document document;

            Console.Write("Loading image ... ");

            Stream imageStream = null;
            try
            {
                imageStream = (defaultImageName == benchmarkImageName) ?
                    Assembly.GetExecutingAssembly().GetManifestResourceStream(benchmarkImageName) :
                    new FileStream(benchmarkImageName, FileMode.Open);

                JpegFileType jft = new JpegFileType();
                document = jft.Load(imageStream);
            }

            finally
            {
                if (imageStream != null)
                {
                    imageStream.Dispose();
                }
            }

            Console.WriteLine("(" + document.Width + " x " + document.Height + ") done");

            Surface surface = ((BitmapLayer)document.Layers[0]).Surface;

#if EFFECTS
            for (double i = 0; i < (2 * Math.PI); i += 70.0 * ((2 * Math.PI) / 360.0))
            {
                benchmarks.Add(
                    new EffectBenchmark("Rotate/Zoom at " + ((i * 180.0) / Math.PI).ToString("F2") + " degrees",
                    3,
                    new PaintDotNet.Effects.RotateZoomEffect(),
                    new PaintDotNet.Effects.RotateZoomEffectConfigToken(
                        true,
                        (float)(Math.PI * 0.3f),
                        (float)((Math.PI * -0.4) + i),
                        50,
                        0.5f,
                        new PointF(-0.2f, 0.3f),
                        false,
                        true),
                        surface));
            }

            for (int i = 1; i <= 4; i += 3)
            {
                for (int j = 10; j < 100; j += 75)
                {
                    benchmarks.Add(
                        new EffectBenchmark(
                            "Oil Painting, brush size = " + i + ", coarseness = " + j,
                            1,
                            new OilPaintingEffect(),
                            new TwoAmountsConfigToken(i, j),
                            surface));
                }
            }

            for (int i = 2; i <= 50; i += i)
            {
                benchmarks.Add(
                    new EffectBenchmark(
                        "Blur with radius of " + i,
                        1,
                        new BlurEffect(),
                        new AmountEffectConfigToken(i),
                        surface));
            }

            for (int i = 1; i <= 4; i += 3)
            {
                benchmarks.Add(
                    new EffectBenchmark(
                        "Sharpen with value of " + i,
                        1,
                        new SharpenEffect(),
                        new AmountEffectConfigToken(i),
                        surface));
            }

            benchmarks.Add(
                new EffectBenchmark(
                    "Auto-Levels",
                    50,
                    new AutoLevelEffect(),
                    null,
                    surface));

            for (int i = 81; i >= 5; i /= 3)
            {
                benchmarks.Add(
                    new EffectBenchmark(
                        "Clouds, roughness = " + i,
                        2,
                        new CloudsEffect(),
                        new CloudsEffectConfigToken(50, i, 12345, new UserBlendOps.NormalBlendOp()),
                        surface));
            }

            for (int i = 4; i <= 64; i *= 4)
            {
                benchmarks.Add(
                    new EffectBenchmark(
                        "Median, radius " + i,
                        1,
                        new MedianEffect(),
                        new TwoAmountsConfigToken(/*radius*/i, /*roughness*/50),
                        surface));
            }

            for (int i = 4; i <= 64; i *= 4)
            {
                benchmarks.Add(
                   new EffectBenchmark(
                       "Unfocus, radius " + i,
                       1,
                       new UnfocusEffect(),
                       new AmountEffectConfigToken(i),
                        surface));
            }

            benchmarks.Add(
                new EffectBenchmark(
                    "Motion Blur, Horizontal",
                    1,
                    new MotionBlurEffect(),
                    new MotionBlurEffectConfigToken(0, 100, true),
                        surface));

            benchmarks.Add(
                new EffectBenchmark(
                    "Motion Blur, Vertical",
                    1,
                    new MotionBlurEffect(),
                    new MotionBlurEffectConfigToken(90, 100, true),
                        surface));

#endif

            Surface dst = new Surface(surface.Width * 4, surface.Height * 4);

#if RESIZE
            // Resize benchmarks
            for (int i = 1; i < 8; i += 2)
            {
                int newWidth = i * (dst.Width / 8);
                int newHeight = i * (dst.Height / 8);

                Surface dstWindow = dst.CreateWindow(new Rectangle(0, 0, newWidth, newHeight));
                benchmarks.Add(new ResizeBenchmark("Resize from " + surface.Width + "x" + surface.Height + " to " + newWidth + "x" + newHeight, surface, dstWindow));
                benchmarks.Add(new ResizeBenchmark("Resize from " + newWidth + "x" + newHeight + " to " + surface.Width + "x" + surface.Height, dstWindow, surface));
            }
#endif

#if GRADIENT
            // Gradient benchmarks
            benchmarks.Add(new GradientBenchmark(
                "Linear reflected gradient @ " + dst.Width + "x" + dst.Height + " (5x)",
                dst,
                new GradientRenderers.LinearReflected(false, new UserBlendOps.NormalBlendOp()),
                2));

            benchmarks.Add(new GradientBenchmark(
                "Conical gradient @ " + dst.Width + "x" + dst.Height + " (5x)",
                dst,
                new GradientRenderers.Conical(false, new UserBlendOps.NormalBlendOp()),
                2));

            benchmarks.Add(new GradientBenchmark(
                "Radial gradient @ " + dst.Width + "x" + dst.Height + " (5x)",
                dst,
                new GradientRenderers.Radial(false, new UserBlendOps.NormalBlendOp()),
                2));
#endif

#if COMPOSITION
            // Composition benchmarks
            Document doc1 = new Document(surface.Size);
            BitmapLayer layer1 = Layer.CreateBackgroundLayer(doc1.Width, doc1.Height);
            layer1.Surface.CopySurface(surface);
            doc1.Layers.Add(layer1);
            doc1.Layers.Add(layer1.Clone());
            doc1.Layers.Add(layer1.Clone());
            doc1.Layers.Add(layer1.Clone());

            benchmarks.Add(new CompositionBenchmark("Compositing one layer, Normal blend mode, 255 opacity (" + CompositionBenchmark.Iterations + "x)",
                doc1,
                surface,
                delegate(int layerIndex, Layer layer)
                {
                    if (layerIndex == 0)
                    {
                        layer.Visible = true;
                        layer.Opacity = 255;
                        ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp());
                    }
                    else
                    {
                        layer.Visible = false;
                    }
                }));

            benchmarks.Add(new CompositionBenchmark("Compositing one layer, Normal blend mode, 128 opacity (" + CompositionBenchmark.Iterations + "x)",
                doc1,
                surface,
                delegate(int layerIndex, Layer layer)
                {
                    if (layerIndex == 0)
                    {
                        layer.Visible = true;
                        layer.Opacity = 128;
                        ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp());
                    }
                    else
                    {
                        layer.Visible = false;
                    }
                }));

            benchmarks.Add(new CompositionBenchmark("Compositing four layers, Normal blend mode, 255 opacity (" + CompositionBenchmark.Iterations + "x)",
                doc1,
                surface,
                delegate(int layerIndex, Layer layer)
                {
                    layer.Visible = true;
                    layer.Opacity = 255;
                    ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp());
                }));

            benchmarks.Add(new CompositionBenchmark("Compositing four layers, Normal blend mode, 255 (layer 0) and 128 (layer 1-3) opacity (" + CompositionBenchmark.Iterations + "x)", doc1, surface,
                delegate(int layerIndex, Layer layer)
                {
                    layer.Visible = true;
                    layer.Opacity = 128;
                    ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp());
                }));

            benchmarks.Add(new CompositionBenchmark("Compositing four layers, Normal blend mode, 128 opacity (" + CompositionBenchmark.Iterations + "x)", doc1, surface,
                delegate(int layerIndex, Layer layer)
                {
                    layer.Visible = true;
                    layer.Opacity = 128;
                    ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp());
                }));

            benchmarks.Add(new CompositionBenchmark("Compositing three layers, Normal+Multiply+Overlay blending, 150+255+170 opacity (" + CompositionBenchmark.Iterations + "x)", doc1, surface,
                delegate(int layerIndex, Layer layer)
                {
                    if (layerIndex == 0)
                    {
                        layer.Visible = true;
                        layer.Opacity = 150;
                        ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp());
                    }
                    else if (layerIndex == 1)
                    {
                        layer.Visible = true;
                        layer.Opacity = 255;
                        ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.MultiplyBlendOp());
                    }
                    else if (layerIndex == 2)
                    {
                        layer.Visible = true;
                        layer.Opacity = 170;
                        ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.OverlayBlendOp());
                    }
                    else
                    {
                        layer.Visible = false;
                    }
                }));
#endif

#if TRANSFORM
            // Transform benchmarks
            Matrix m = new Matrix();
            m.Reset();

            MaskedSurface msSimple = new MaskedSurface(surface, new PdnRegion(surface.Bounds)); // simple masked surface

            PdnRegion complexRegion = new PdnRegion(surface.Bounds);

            // cut 4 holes in region 1 to form a complex clipping surface
            for (int x = -1; x < 3; ++x)
            {
                for (int y = -1; y < 3; ++y)
                {
                    int left = (1 + (x * 3)) * (surface.Width / 6);
                    int top = (1 + (x * 3)) * (surface.Height / 6);
                    int right = (2 + (x * 3)) * (surface.Width / 6);
                    int bottom = (2 + (x * 3)) * (surface.Height / 6);

                    Rectangle rect = Rectangle.FromLTRB(left, top, right, bottom);
                    PdnGraphicsPath path = new PdnGraphicsPath();
                    path.AddEllipse(rect);
                    complexRegion.Exclude(path);
                }
            }

            MaskedSurface msComplex = new MaskedSurface(surface, complexRegion);

            benchmarks.Add(new TransformBenchmark("Transform simple surface, no transform, nearest neighbor resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m,
                false));

            benchmarks.Add(new TransformBenchmark("Transform complex surface, no transform, nearest neighbor resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m,
                false));

            benchmarks.Add(new TransformBenchmark("Transform simple surface, no transform, bilinear resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m,
                true));

            benchmarks.Add(new TransformBenchmark("Transform complex surface, no transform, bilinear resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m,
                true));

            Matrix m2 = m.Clone();
            m2.RotateAt(45.0f, new PointF(surface.Width / 2, surface.Height / 2));

            benchmarks.Add(new TransformBenchmark("Transform simple surface, 45 deg. rotation about center, bilinear resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m2,
                true));

            benchmarks.Add(new TransformBenchmark("Transform complex surface, 45 deg. rotation about center, bilinear resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m2,
                true));

            Matrix m3 = m.Clone();
            m3.Scale(0.5f, 0.75f);

            benchmarks.Add(new TransformBenchmark("Transform simple surface, 50% x-scaling 75% y-scaling, bilinear resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m3,
                true));

            benchmarks.Add(new TransformBenchmark("Transform complex surface, 50% x-scaling 75% y-scaling, bilinear resampling (" + TransformBenchmark.Iterations + "x)",
                surface,
                msSimple,
                m3,
                true));
#endif

#if BLIT
            // Blit benchmarks
            benchmarks.Add(new ZoomOutBlitBenchmark("Zoom out, rotated grid multisampling, 66% (" + ZoomOutBlitBenchmark.IterationCount + "x)",
                surface,
                dst,
                new Size((surface.Width * 2) / 3, (surface.Height * 2) / 3)));

            benchmarks.Add(new ZoomOutBlitBenchmark("Zoom out, rotated grid multisampling, 28% (" + ZoomOutBlitBenchmark.IterationCount + "x)",
                surface,
                dst,
                new Size((surface.Width * 28) / 100, (surface.Height * 28) / 100)));

            benchmarks.Add(new ZoomOneToOneBlitBenchmark("Zoom 1:1, straight blit (" + ZoomOneToOneBlitBenchmark.IterationCount + "x)",
                surface,
                dst.CreateWindow(new Rectangle(0, 0, surface.Width, surface.Height))));
#endif

            // Run benchmarks!
            Timing timing = new Timing();
            ulong start = timing.GetTickCount();

            foreach (Benchmark benchmark in benchmarks)
            {
                Console.Write(benchmark.Name + (useTsvOutput ? "\t" : " ... "));
                TimeSpan timeSpan = benchmark.Execute();
                Console.WriteLine(" " + timeSpan.TotalMilliseconds.ToString() + (useTsvOutput ? "\t" : "") + " milliseconds");

                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }

            ulong end = timing.GetTickCount();

            Console.WriteLine();
            Console.WriteLine("Total time: " + (useTsvOutput ? "\t" : "") + (end - start).ToString() + (useTsvOutput ? "\t" : "") + " milliseconds");
            Console.WriteLine();
        }
        private void DrawGradient(Graphics g)
        {
            g.PixelOffsetMode = PixelOffsetMode.Half;
            Rectangle gradientRect;

            float gradientAngle;

            switch (this.orientation)
            {
                case Orientation.Horizontal:
                    gradientAngle = 180.0f;
                    break;

                case Orientation.Vertical:
                    gradientAngle = 90.0f;
                    break;

                default:
                    throw new InvalidEnumArgumentException();
            }

            // draw gradient
            gradientRect = ClientRectangle;

            switch (this.orientation)
            {
                case Orientation.Horizontal:
                    gradientRect.Inflate(-triangleHalfLength, -triangleSize + 3);
                    break;

                case Orientation.Vertical:
                    gradientRect.Inflate(-triangleSize + 3, -triangleHalfLength);
                    break;

                default:
                    throw new InvalidEnumArgumentException();
            }

            if (this.customGradient != null && gradientRect.Width > 1 && gradientRect.Height > 1)
            {
                Surface gradientSurface = new Surface(gradientRect.Size);

                using (RenderArgs ra = new RenderArgs(gradientSurface))
                {
                    Utility.DrawColorRectangle(ra.Graphics, ra.Bounds, Color.Transparent, false);

                    if (Orientation == Orientation.Horizontal)
                    {
                        for (int x = 0; x < gradientSurface.Width; ++x)
                        {
                            // TODO: refactor, double buffer, save this computation in a bitmap somewhere
                            double index = (double)(x * (this.customGradient.Length - 1)) / (double)(gradientSurface.Width - 1);
                            int indexL = (int)Math.Floor(index);
                            double t = 1.0 - (index - indexL);
                            int indexR = (int)Math.Min(this.customGradient.Length - 1, Math.Ceiling(index));
                            Color colorL = this.customGradient[indexL];
                            Color colorR = this.customGradient[indexR];

                            double a1 = colorL.A / 255.0;
                            double r1 = colorL.R / 255.0;
                            double g1 = colorL.G / 255.0;
                            double b1 = colorL.B / 255.0;

                            double a2 = colorR.A / 255.0;
                            double r2 = colorR.R / 255.0;
                            double g2 = colorR.G / 255.0;
                            double b2 = colorR.B / 255.0;

                            double at = (t * a1) + ((1.0 - t) * a2);

                            double rt;
                            double gt;
                            double bt;
                            if (at == 0)
                            {
                                rt = 0;
                                gt = 0;
                                bt = 0;
                            }
                            else
                            {
                                rt = ((t * a1 * r1) + ((1.0 - t) * a2 * r2)) / at;
                                gt = ((t * a1 * g1) + ((1.0 - t) * a2 * g2)) / at;
                                bt = ((t * a1 * b1) + ((1.0 - t) * a2 * b2)) / at;
                            }

                            int ap = Utility.Clamp((int)Math.Round(at * 255.0), 0, 255);
                            int rp = Utility.Clamp((int)Math.Round(rt * 255.0), 0, 255);
                            int gp = Utility.Clamp((int)Math.Round(gt * 255.0), 0, 255);
                            int bp = Utility.Clamp((int)Math.Round(bt * 255.0), 0, 255);

                            for (int y = 0; y < gradientSurface.Height; ++y)
                            {
                                ColorBgra src = gradientSurface[x, y];

                                // we are assuming that src.A = 255

                                int rd = ((rp * ap) + (src.R * (255 - ap))) / 255;
                                int gd = ((gp * ap) + (src.G * (255 - ap))) / 255;
                                int bd = ((bp * ap) + (src.B * (255 - ap))) / 255;

                                // TODO: proper alpha blending!
                                gradientSurface[x, y] = ColorBgra.FromBgra((byte)bd, (byte)gd, (byte)rd, 255);
                            }
                        }

                        g.DrawImage(ra.Bitmap, gradientRect, ra.Bounds, GraphicsUnit.Pixel);
                    }
                    else if (Orientation == Orientation.Vertical)
                    {
                        // TODO
                    }
                    else
                    {
                        throw new InvalidEnumArgumentException();
                    }
                }

                gradientSurface.Dispose();
            }
            else
            {
                using (LinearGradientBrush lgb = new LinearGradientBrush(this.ClientRectangle,
                           maxColor, minColor, gradientAngle, false))
                {
                    g.FillRectangle(lgb, gradientRect);
                }
            }

            // fill background
            using (PdnRegion nonGradientRegion = new PdnRegion())
            {
                nonGradientRegion.MakeInfinite();
                nonGradientRegion.Exclude(gradientRect);

                using (SolidBrush sb = new SolidBrush(this.BackColor))
                {
                    g.FillRegion(sb, nonGradientRegion.GetRegionReadOnly());
                }
            }

            // draw value triangles
            for (int i = 0; i < this.vals.Length; i++)
            {
                int pos = ValueToPosition(vals[i]);
                Brush brush;
                Pen pen;

                if (i == highlight) 
                {
                    brush = Brushes.Blue;
                    pen = (Pen)Pens.White.Clone();
                } 
                else 
                {
                    brush = Brushes.Black;
                    pen = (Pen)Pens.Gray.Clone();
                }

                g.SmoothingMode = SmoothingMode.AntiAlias;

                Point a1;
                Point b1;
                Point c1;

                Point a2;
                Point b2;
                Point c2;

                switch (this.orientation)
                {
                    case Orientation.Horizontal:
                        a1 = new Point(pos - triangleHalfLength, 0);
                        b1 = new Point(pos, triangleSize - 1);
                        c1 = new Point(pos + triangleHalfLength, 0);

                        a2 = new Point(a1.X, Height - 1 - a1.Y);
                        b2 = new Point(b1.X, Height - 1 - b1.Y);
                        c2 = new Point(c1.X, Height - 1 - c1.Y);
                        break;

                    case Orientation.Vertical:
                        a1 = new Point(0, pos - triangleHalfLength);
                        b1 = new Point(triangleSize - 1, pos);
                        c1 = new Point(0, pos + triangleHalfLength);

                        a2 = new Point(Width - 1 - a1.X, a1.Y);
                        b2 = new Point(Width - 1 - b1.X, b1.Y);
                        c2 = new Point(Width - 1 - c1.X, c1.Y);
                        break;

                    default:
                        throw new InvalidEnumArgumentException();
                }

                if (this.drawNearNub)
                {
                    g.FillPolygon(brush, new Point[] { a1, b1, c1, a1 });
                }

                if (this.drawFarNub)
                {
                    g.FillPolygon(brush, new Point[] { a2, b2, c2, a2 });
                }

                if (pen != null)
                {
                    if (this.drawNearNub)
                    {
                        g.DrawPolygon(pen, new Point[] { a1, b1, c1, a1 });
                    }

                    if (this.drawFarNub)
                    {
                        g.DrawPolygon(pen, new Point[] { a2, b2, c2, a2 });
                    }

                    pen.Dispose();
                }
            }
        }
Exemple #3
0
        private void DrawShadow(Graphics g, Rectangle clipRect)
        {
            if (this.occludingControl != null)
            {
                // Draw the outline rectangle
                Rectangle outlineRect = new Rectangle(new Point(0, 0), this.occludingControl.Size);

                outlineRect = occludingControl.RectangleToScreen(outlineRect);
                outlineRect = RectangleToClient(outlineRect);
                outlineRect.X -= 1;
                outlineRect.Y -= 1;
                outlineRect.Width += 2;
                outlineRect.Height += 2;

                g.DrawLines(
                    Pens.Black,
                    new Point[]
                    {
                        new Point(outlineRect.Left, outlineRect.Top),
                        new Point(outlineRect.Right, outlineRect.Top),
                        new Point(outlineRect.Right, outlineRect.Bottom),
                        new Point(outlineRect.Left, outlineRect.Bottom),
                        new Point(outlineRect.Left, outlineRect.Top)
                    }); 
                
                using (PdnRegion backRegion = new PdnRegion(clipRect))
                {
                    Rectangle occludingRect = new Rectangle(0, 0, this.occludingControl.Width, this.occludingControl.Height);
                    occludingRect = this.occludingControl.RectangleToScreen(occludingRect);
                    occludingRect = RectangleToClient(occludingRect);
                    backRegion.Exclude(occludingRect);
                    backRegion.Exclude(outlineRect);

                    using (Brush backBrush = new SolidBrush(this.BackColor))
                    {
                        g.FillRegion(backBrush, backRegion.GetRegionReadOnly());
                    }
                }
                
                Rectangle edgeRect = new Rectangle(0, 0, this.roundedEdgeUL.Width, this.roundedEdgeUR.Height);

                Rectangle ulEdgeRect = new Rectangle(outlineRect.Left - 3, outlineRect.Top - 3, this.roundedEdgeUL.Width, this.roundedEdgeUL.Height);
                Rectangle urEdgeRect = new Rectangle(outlineRect.Right - 3, outlineRect.Top - 3, this.roundedEdgeUR.Width, this.roundedEdgeUR.Height);
                Rectangle llEdgeRect = new Rectangle(outlineRect.Left - 3, outlineRect.Bottom - 3, this.roundedEdgeLL.Width, this.roundedEdgeLL.Height);
                Rectangle lrEdgeRect = new Rectangle(outlineRect.Right - 3, outlineRect.Bottom - 3, this.roundedEdgeLR.Width, this.roundedEdgeLR.Height);

                g.DrawImage(this.roundedEdgeUL, ulEdgeRect, edgeRect, GraphicsUnit.Pixel);
                g.DrawImage(this.roundedEdgeUR, urEdgeRect, edgeRect, GraphicsUnit.Pixel);
                g.DrawImage(this.roundedEdgeLL, llEdgeRect, edgeRect, GraphicsUnit.Pixel);
                g.DrawImage(this.roundedEdgeLR, lrEdgeRect, edgeRect, GraphicsUnit.Pixel);

                Color c1 = Color.FromArgb(95, Color.Black);
                Color c2 = Color.FromArgb(47, Color.Black);
                Color c3 = Color.FromArgb(15, Color.Black);

                Pen p1 = new Pen(c1);
                Pen p2 = new Pen(c2);
                Pen p3 = new Pen(c3);

                // Draw top soft edge
                g.DrawLine(p1, ulEdgeRect.Right, outlineRect.Top - 1, urEdgeRect.Left - 1, outlineRect.Top - 1);
                g.DrawLine(p2, ulEdgeRect.Right, outlineRect.Top - 2, urEdgeRect.Left - 1, outlineRect.Top - 2);
                g.DrawLine(p3, ulEdgeRect.Right, outlineRect.Top - 3, urEdgeRect.Left - 1, outlineRect.Top - 3);

                // Draw bottom soft edge
                g.DrawLine(p1, llEdgeRect.Right, outlineRect.Bottom + 0, lrEdgeRect.Left - 1, outlineRect.Bottom + 0);
                g.DrawLine(p2, llEdgeRect.Right, outlineRect.Bottom + 1, lrEdgeRect.Left - 1, outlineRect.Bottom + 1);
                g.DrawLine(p3, llEdgeRect.Right, outlineRect.Bottom + 2, lrEdgeRect.Left - 1, outlineRect.Bottom + 2);

                // Draw left soft edge
                g.DrawLine(p1, outlineRect.Left - 1, ulEdgeRect.Bottom, outlineRect.Left - 1, llEdgeRect.Top - 1);
                g.DrawLine(p2, outlineRect.Left - 2, ulEdgeRect.Bottom, outlineRect.Left - 2, llEdgeRect.Top - 1);
                g.DrawLine(p3, outlineRect.Left - 3, ulEdgeRect.Bottom, outlineRect.Left - 3, llEdgeRect.Top - 1);

                // Draw right soft edge
                g.DrawLine(p1, outlineRect.Right + 0, urEdgeRect.Bottom, outlineRect.Right + 0, lrEdgeRect.Top - 1);
                g.DrawLine(p2, outlineRect.Right + 1, urEdgeRect.Bottom, outlineRect.Right + 1, lrEdgeRect.Top - 1);
                g.DrawLine(p3, outlineRect.Right + 2, urEdgeRect.Bottom, outlineRect.Right + 2, lrEdgeRect.Top - 1);

                p1.Dispose();
                p1 = null;

                p2.Dispose();
                p2 = null;

                p3.Dispose();
                p3 = null;
            }

            if (this.betaTagString != null)
            {
                Color betaTagColor = Color.FromArgb(this.betaTagOpacity, SystemColors.WindowText);
                Brush betaTagBrush = new SolidBrush(betaTagColor);
                StringFormat sf = (StringFormat)StringFormat.GenericTypographic.Clone();

                sf.Alignment = StringAlignment.Center;
                sf.LineAlignment = StringAlignment.Near;

                g.DrawString(
                    this.betaTagString,
                    this.Font,
                    betaTagBrush,
                    ClientRectangle.Width / 2,
                    1,
                    sf);

                sf.Dispose();
                sf = null;

                betaTagBrush.Dispose();
                betaTagBrush = null;
            }
        }
        private void DrawShadow(Graphics g, Rectangle clipRect)
        {
            if (occludingControl != null)
            {
                // Draw the outline rectangle
                Rectangle outlineRect = new Rectangle(new Point(0, 0), occludingControl.Size);

                outlineRect         = occludingControl.RectangleToScreen(outlineRect);
                outlineRect         = RectangleToClient(outlineRect);
                outlineRect.X      -= 1;
                outlineRect.Y      -= 1;
                outlineRect.Width  += 2;
                outlineRect.Height += 2;

                g.DrawLines(
                    Pens.Black,
                    new Point[]
                {
                    new Point(outlineRect.Left, outlineRect.Top),
                    new Point(outlineRect.Right, outlineRect.Top),
                    new Point(outlineRect.Right, outlineRect.Bottom),
                    new Point(outlineRect.Left, outlineRect.Bottom),
                    new Point(outlineRect.Left, outlineRect.Top)
                });

                using (PdnRegion backRegion = new PdnRegion(clipRect))
                {
                    Rectangle occludingRect = new Rectangle(0, 0, this.occludingControl.Width, this.occludingControl.Height);
                    occludingRect = this.occludingControl.RectangleToScreen(occludingRect);
                    occludingRect = RectangleToClient(occludingRect);
                    backRegion.Exclude(occludingRect);
                    backRegion.Exclude(outlineRect);

                    if (ClientSize.Height != this.gradientHeight)
                    {
                        if (this.gradientBrush != null)
                        {
                            this.gradientBrush.Dispose();
                            this.gradientBrush = null;
                        }

                        this.gradientBrush = new LinearGradientBrush(
                            new Point(0, 0),
                            new Point(0, ClientSize.Height),
                            gradientTop,
                            gradientBottom);
                    }

                    g.FillRegion(this.gradientBrush, backRegion.GetRegionReadOnly());
                }


                Rectangle edgeRect = new Rectangle(0, 0, this.roundedEdgeUL.Width, this.roundedEdgeUR.Height);

                Rectangle ulEdgeRect = new Rectangle(outlineRect.Left - 3, outlineRect.Top - 3, this.roundedEdgeUL.Width, this.roundedEdgeUL.Height);
                Rectangle urEdgeRect = new Rectangle(outlineRect.Right - 3, outlineRect.Top - 3, this.roundedEdgeUR.Width, this.roundedEdgeUR.Height);
                Rectangle llEdgeRect = new Rectangle(outlineRect.Left - 3, outlineRect.Bottom - 3, this.roundedEdgeLL.Width, this.roundedEdgeLL.Height);
                Rectangle lrEdgeRect = new Rectangle(outlineRect.Right - 3, outlineRect.Bottom - 3, this.roundedEdgeLR.Width, this.roundedEdgeLR.Height);

                g.DrawImage(this.roundedEdgeUL, ulEdgeRect, edgeRect, GraphicsUnit.Pixel);
                g.DrawImage(this.roundedEdgeUR, urEdgeRect, edgeRect, GraphicsUnit.Pixel);
                g.DrawImage(this.roundedEdgeLL, llEdgeRect, edgeRect, GraphicsUnit.Pixel);
                g.DrawImage(this.roundedEdgeLR, lrEdgeRect, edgeRect, GraphicsUnit.Pixel);

                Color c1 = Color.FromArgb(95, Color.Black);
                Color c2 = Color.FromArgb(47, Color.Black);
                Color c3 = Color.FromArgb(15, Color.Black);

                Pen p1 = new Pen(c1);
                Pen p2 = new Pen(c2);
                Pen p3 = new Pen(c3);

                // Draw top soft edge
                g.DrawLine(p1, ulEdgeRect.Right, outlineRect.Top - 1, urEdgeRect.Left - 1, outlineRect.Top - 1);
                g.DrawLine(p2, ulEdgeRect.Right, outlineRect.Top - 2, urEdgeRect.Left - 1, outlineRect.Top - 2);
                g.DrawLine(p3, ulEdgeRect.Right, outlineRect.Top - 3, urEdgeRect.Left - 1, outlineRect.Top - 3);

                // Draw bottom soft edge
                g.DrawLine(p1, llEdgeRect.Right, outlineRect.Bottom + 0, lrEdgeRect.Left - 1, outlineRect.Bottom + 0);
                g.DrawLine(p2, llEdgeRect.Right, outlineRect.Bottom + 1, lrEdgeRect.Left - 1, outlineRect.Bottom + 1);
                g.DrawLine(p3, llEdgeRect.Right, outlineRect.Bottom + 2, lrEdgeRect.Left - 1, outlineRect.Bottom + 2);

                // Draw left soft edge
                g.DrawLine(p1, outlineRect.Left - 1, ulEdgeRect.Bottom, outlineRect.Left - 1, llEdgeRect.Top - 1);
                g.DrawLine(p2, outlineRect.Left - 2, ulEdgeRect.Bottom, outlineRect.Left - 2, llEdgeRect.Top - 1);
                g.DrawLine(p3, outlineRect.Left - 3, ulEdgeRect.Bottom, outlineRect.Left - 3, llEdgeRect.Top - 1);

                // Draw right soft edge
                g.DrawLine(p1, outlineRect.Right + 0, urEdgeRect.Bottom, outlineRect.Right + 0, lrEdgeRect.Top - 1);
                g.DrawLine(p2, outlineRect.Right + 1, urEdgeRect.Bottom, outlineRect.Right + 1, lrEdgeRect.Top - 1);
                g.DrawLine(p3, outlineRect.Right + 2, urEdgeRect.Bottom, outlineRect.Right + 2, lrEdgeRect.Top - 1);

                p1.Dispose();
                p1 = null;

                p2.Dispose();
                p2 = null;

                p3.Dispose();
                p3 = null;
            }

            if (this.betaTagString != null)
            {
                Color        betaTagColor = Color.FromArgb(this.betaTagOpacity, SystemColors.WindowText);
                Brush        betaTagBrush = new SolidBrush(betaTagColor);
                StringFormat sf           = (StringFormat)StringFormat.GenericTypographic.Clone();

                sf.Alignment     = StringAlignment.Center;
                sf.LineAlignment = StringAlignment.Near;

                g.DrawString(
                    this.betaTagString,
                    this.Font,
                    betaTagBrush,
                    ClientRectangle.Width / 2,
                    1,
                    sf);

                sf.Dispose();
                sf = null;

                betaTagBrush.Dispose();
                betaTagBrush = null;
            }
        }
        private void DrawGradient(Graphics g)
        {
            g.PixelOffsetMode = PixelOffsetMode.Half;
            Rectangle gradientRect;

            float gradientAngle;

            switch (this.orientation)
            {
            case Orientation.Horizontal:
                gradientAngle = 180.0f;
                break;

            case Orientation.Vertical:
                gradientAngle = 90.0f;
                break;

            default:
                throw new InvalidEnumArgumentException();
            }

            // draw gradient
            gradientRect = ClientRectangle;

            switch (this.orientation)
            {
            case Orientation.Horizontal:
                gradientRect.Inflate(-triangleHalfLength, -triangleSize + 3);
                break;

            case Orientation.Vertical:
                gradientRect.Inflate(-triangleSize + 3, -triangleHalfLength);
                break;

            default:
                throw new InvalidEnumArgumentException();
            }

            if (this.customGradient != null && gradientRect.Width > 1 && gradientRect.Height > 1)
            {
                Surface gradientSurface = new Surface(gradientRect.Size);

                using (RenderArgs ra = new RenderArgs(gradientSurface))
                {
                    Utility.DrawColorRectangle(ra.Graphics, ra.Bounds, Color.Transparent, false);

                    if (Orientation == Orientation.Horizontal)
                    {
                        for (int x = 0; x < gradientSurface.Width; ++x)
                        {
                            // TODO: refactor, double buffer, save this computation in a bitmap somewhere
                            double index  = (double)(x * (this.customGradient.Length - 1)) / (double)(gradientSurface.Width - 1);
                            int    indexL = (int)Math.Floor(index);
                            double t      = 1.0 - (index - indexL);
                            int    indexR = (int)Math.Min(this.customGradient.Length - 1, Math.Ceiling(index));
                            Color  colorL = this.customGradient[indexL];
                            Color  colorR = this.customGradient[indexR];

                            double a1 = colorL.A / 255.0;
                            double r1 = colorL.R / 255.0;
                            double g1 = colorL.G / 255.0;
                            double b1 = colorL.B / 255.0;

                            double a2 = colorR.A / 255.0;
                            double r2 = colorR.R / 255.0;
                            double g2 = colorR.G / 255.0;
                            double b2 = colorR.B / 255.0;

                            double at = (t * a1) + ((1.0 - t) * a2);

                            double rt;
                            double gt;
                            double bt;
                            if (at == 0)
                            {
                                rt = 0;
                                gt = 0;
                                bt = 0;
                            }
                            else
                            {
                                rt = ((t * a1 * r1) + ((1.0 - t) * a2 * r2)) / at;
                                gt = ((t * a1 * g1) + ((1.0 - t) * a2 * g2)) / at;
                                bt = ((t * a1 * b1) + ((1.0 - t) * a2 * b2)) / at;
                            }

                            int ap = Utility.Clamp((int)Math.Round(at * 255.0), 0, 255);
                            int rp = Utility.Clamp((int)Math.Round(rt * 255.0), 0, 255);
                            int gp = Utility.Clamp((int)Math.Round(gt * 255.0), 0, 255);
                            int bp = Utility.Clamp((int)Math.Round(bt * 255.0), 0, 255);

                            for (int y = 0; y < gradientSurface.Height; ++y)
                            {
                                ColorBgra src = gradientSurface[x, y];

                                // we are assuming that src.A = 255

                                int rd = ((rp * ap) + (src.R * (255 - ap))) / 255;
                                int gd = ((gp * ap) + (src.G * (255 - ap))) / 255;
                                int bd = ((bp * ap) + (src.B * (255 - ap))) / 255;

                                // TODO: proper alpha blending!
                                gradientSurface[x, y] = ColorBgra.FromBgra((byte)bd, (byte)gd, (byte)rd, 255);
                            }
                        }

                        g.DrawImage(ra.Bitmap, gradientRect, ra.Bounds, GraphicsUnit.Pixel);
                    }
                    else if (Orientation == Orientation.Vertical)
                    {
                        // TODO
                    }
                    else
                    {
                        throw new InvalidEnumArgumentException();
                    }
                }

                gradientSurface.Dispose();
            }
            else
            {
                using (LinearGradientBrush lgb = new LinearGradientBrush(this.ClientRectangle,
                                                                         maxColor, minColor, gradientAngle, false))
                {
                    g.FillRectangle(lgb, gradientRect);
                }
            }

            // fill background
            using (PdnRegion nonGradientRegion = new PdnRegion())
            {
                nonGradientRegion.MakeInfinite();
                nonGradientRegion.Exclude(gradientRect);

                using (SolidBrush sb = new SolidBrush(this.BackColor))
                {
                    g.FillRegion(sb, nonGradientRegion.GetRegionReadOnly());
                }
            }

            // draw value triangles
            for (int i = 0; i < this.vals.Length; i++)
            {
                int   pos = ValueToPosition(vals[i]);
                Brush brush;
                Pen   pen;

                if (i == highlight)
                {
                    brush = Brushes.Blue;
                    pen   = (Pen)Pens.White.Clone();
                }
                else
                {
                    brush = Brushes.Black;
                    pen   = (Pen)Pens.Gray.Clone();
                }

                g.SmoothingMode = SmoothingMode.AntiAlias;

                Point a1;
                Point b1;
                Point c1;

                Point a2;
                Point b2;
                Point c2;

                switch (this.orientation)
                {
                case Orientation.Horizontal:
                    a1 = new Point(pos - triangleHalfLength, 0);
                    b1 = new Point(pos, triangleSize - 1);
                    c1 = new Point(pos + triangleHalfLength, 0);

                    a2 = new Point(a1.X, Height - 1 - a1.Y);
                    b2 = new Point(b1.X, Height - 1 - b1.Y);
                    c2 = new Point(c1.X, Height - 1 - c1.Y);
                    break;

                case Orientation.Vertical:
                    a1 = new Point(0, pos - triangleHalfLength);
                    b1 = new Point(triangleSize - 1, pos);
                    c1 = new Point(0, pos + triangleHalfLength);

                    a2 = new Point(Width - 1 - a1.X, a1.Y);
                    b2 = new Point(Width - 1 - b1.X, b1.Y);
                    c2 = new Point(Width - 1 - c1.X, c1.Y);
                    break;

                default:
                    throw new InvalidEnumArgumentException();
                }

                if (this.drawNearNub)
                {
                    g.FillPolygon(brush, new Point[] { a1, b1, c1, a1 });
                }

                if (this.drawFarNub)
                {
                    g.FillPolygon(brush, new Point[] { a2, b2, c2, a2 });
                }

                if (pen != null)
                {
                    if (this.drawNearNub)
                    {
                        g.DrawPolygon(pen, new Point[] { a1, b1, c1, a1 });
                    }

                    if (this.drawFarNub)
                    {
                        g.DrawPolygon(pen, new Point[] { a2, b2, c2, a2 });
                    }

                    pen.Dispose();
                }
            }
        }