/// <summary> Compresses a bitmap using TrigradCompression. </summary>
        /// <param name="bitmap"> The input bitmap.</param>
        /// <param name="options"> TrigradOptions specifying how the image will be compressed.</param>
        public static TrigradCompressed CompressBitmap(Bitmap bitmap, TrigradOptions options)
        {
            TrigradCompressed compressed = new TrigradCompressed { Height = bitmap.Height, Width = bitmap.Width };
            List<Point> samplePoints = new List<Point>();

            samplePoints.Add(new Point(0, 0));
            samplePoints.Add(new Point(bitmap.Width - 1, 0));
            samplePoints.Add(new Point(0, bitmap.Height - 1));
            samplePoints.Add(new Point(bitmap.Width - 1, bitmap.Height - 1));

            double baseChance = 1d / ((double)(bitmap.Width * bitmap.Height) / options.SampleCount / 8);

            for (int x = 0; x < bitmap.Width; x++)
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    double chance = ((options.FrequencyTable != null) ? options.FrequencyTable.Table[x, y] : 1d) * baseChance;

                    if (options.Random.NextDouble() < chance)
                    {
                        samplePoints.Add(new Point(x, y));
                    }
                }
            }

            foreach (var sample in samplePoints)
            {
                List<Color> averageColors = new List<Color>();

                for (int x = sample.X - options.SampleRadius; x < sample.X + options.SampleRadius + 1; x++)
                {
                    for (int y = sample.Y - options.SampleRadius; y < sample.Y + options.SampleRadius + 1; y++)
                    {
                        if (y >= 0 && y < bitmap.Height && x >= 0 && x < bitmap.Width)
                        {
                            averageColors.Add(bitmap.GetPixel(x, y));
                        }
                    }
                }

                byte R = (byte)averageColors.Average(c => c.R);
                byte G = (byte)averageColors.Average(c => c.G);
                byte B = (byte)averageColors.Average(c => c.B);
                compressed.SampleTable[sample] = Color.FromArgb(R, G, B);
            }

            return compressed;
        }
        /// <summary> Decompresses a trigrad compressed bitmap. </summary>
        /// <param name="compressionData"> The TrigradCompressed data.</param>
        /// <param name="colorGrader"> The color grader that will be used to fill the output bitmap.</param>
        /// <param name="debug"> Bool specifying whether a debug output will be produced.</param>
        public static TrigradDecompressed DecompressBitmap(TrigradCompressed compressionData, IGrader colorGrader = null,bool debug = false)
        {
            if (colorGrader == null)
                colorGrader = new BarycentricGrader();

            TrigradDecompressed decompressed = new TrigradDecompressed(compressionData.Width, compressionData.Height);

            //build the triangle mesh
            decompressed.Mesh = buildMesh(compressionData.SampleTable);

            Parallel.ForEach(decompressed.Mesh.Triangles, triangle =>
            {
                var vU = triangle.GetVertex(0);
                var vV = triangle.GetVertex(1);
                var vW = triangle.GetVertex(2);

                //find sample points of triangle
                Color cU = compressionData.SampleTable[vU.Point()];
                Color cV = compressionData.SampleTable[vV.Point()];
                Color cW = compressionData.SampleTable[vW.Point()];

                //rasterize triangle to find points to fill
                var points = TriangleRasterization.PointsInTriangle(vU.Point(), vV.Point(), vW.Point());

                foreach (var point in points)
                {
                    var coords = Barycentric.GetCoordinates(point, vU, vV, vW);

                    Color gradedColor = colorGrader.Grade(cU, cV, cW, coords.U, coords.V, coords.W, point.X, point.Y);

                    lock (decompressed.Output)
                        decompressed.Output.SetPixel(point.X, point.Y, gradedColor);

                    if(debug)
                    lock (decompressed.DebugOutput)
                        decompressed.DebugOutput.SetPixel(point.X, point.Y, Color.FromArgb((byte)(coords.U * 255), (byte)(coords.V * 255), (byte)(coords.W * 255)));

                }
            });

            return decompressed;
        }