Пример #1
0
        public CircleArc(Circle2D circle, double startAngle, double sweepAngle)
        {
            // For negative sweep angles we need to adjust the start angle so that we can make the sweep
            // angle positive and be representing the same arc.
            if (sweepAngle < 0.0)
            {
                startAngle += sweepAngle;
                sweepAngle = -sweepAngle;
            }

            // Get the start angle in the range [0, 2*pi)
            if (startAngle < 0.0)
            {
                startAngle = (startAngle % c_twoPi) + c_twoPi;
            }
            else
            {
                startAngle = startAngle % c_twoPi;
            }
            // Get the sweep angle in the range [0, 2*pi). Note that it's non-negative at this point.
            sweepAngle %= c_twoPi;

            m_circle = circle;
            m_startAngle = startAngle;
            m_sweepAngle = sweepAngle;
        }
Пример #2
0
 public static void Swap(ref Circle2D a, ref Circle2D b)
 {
     Circle2D temp = a;
     a = b;
     b = temp;
 }
Пример #3
0
        public unsafe void FillCircle(SimpleImage img, uint clr, Circle2D circle)
        {
            // Optimization: For composite-blending, we don't have do draw anything if the
            // drawing color has an alpha value of zero
            if (0 == (clr & 0xFF000000))
            {
                return;
            }

            // Soften radius (hard-coded for now)
            float sr = 2.0f;

            // Compute the inclusive y-starting point
            int ystart = (int)Math.Floor(circle.MinY);
            // Compute the inclusive y-ending point
            int yend = (int)Math.Ceiling(circle.MaxY);

            // Do a quick check to see if we're completely off the image
            if (yend < 0 || ystart >= img.Height)
            {
                return;
            }

            // Constrain y-values, if needed
            if (ystart < 0)
            {
                ystart = 0;
            }
            if (yend > img.Height - 1)
            {
                yend = img.Height - 1;
            }

            // Compute a float alpha value in the range [0.0, 1.0]
            float alpha = (float)(clr >> 24) / 255.0f;

            // Loop through rows of pixels
            for (; ystart <= yend; ystart++)
            {
                // Compute intersections for this row
                float minx, maxx;
                circle.HLineIntersection((float)ystart, out minx, out maxx);

                // Skip to the next row if there was no intersection
                if (float.IsNaN(minx) || float.IsNaN(maxx))
                {
                    continue;
                }

                // Compute the starting point on the left edge of the circle
                int xLeft = (int)Math.Floor(minx);
                // Skip this row if it's off the image
                if (xLeft >= img.Width)
                {
                    continue;
                }
                // Constrain if necessary
                if (xLeft < 0)
                {
                    xLeft = 0;
                }

                // Now compute the starting point on the right edge of the circle
                int xRight = (int)Math.Ceiling(maxx);
                // Skip if we're completely off the image
                if (xRight < 0)
                {
                    continue;
                }
                // Constrain, if necessary
                if (xRight >= img.Width)
                {
                    xRight = img.Width - 1;
                }

                // Make a pointer to this row of pixels
                uint* row = &img.Pixels[ystart * img.Width + xLeft];

                // Draw from the left edge until we hit a point where we're no longer within
                // the softening radius distance from the edge
                while (xLeft <= xRight)
                {
                    float dist = circle.DistanceFromCenter((float)xLeft, (float)ystart);
                    if (dist >= circle.Radius)
                    {
                        // Advance to next pixel
                        row++;
                        xLeft++;
                        continue;
                    }
                    if (dist <= circle.Radius - sr)
                    {
                        break;
                    }

                    // Compute the alpha value
                    float newAFloat = (circle.Radius - dist) / sr * alpha * 255.0f;
                    // Blend
                    BlendComposite((clr & 0x00FFffFF) | (((uint)newAFloat) << 24), row);

                    // Advance to next pixel
                    row++;
                    xLeft++;

                    // See if we end up heading off the right edge of the image
                    if (xLeft >= img.Width)
                    {
                        break;
                    }
                }

                // If we drew pixels until we went off the image, then are done with this row
                if (xLeft >= img.Width)
                {
                    continue;
                }

                // It's possible, depending on the softening radius and which part of the circle we're
                // rendering, that the entire row has already been processed. Remember that xLeft at
                // this point is one to the right of the last pixel that was processed.
                if (xRight < xLeft)
                {
                    continue;
                }

                // Set the pointer to where we're starting
                row = &img.Pixels[ystart * img.Width + xRight];

                // Draw from the right edge until we hit a point where we're:
                //  a) no longer within the softening radius distance from the edge
                //  b) we're off the image (xRight < 0)
                //  or
                //  c) we're less than xLeft
                while (xRight >= 0)
                {
                    float dist = circle.DistanceFromCenter((float)xRight, (float)ystart);
                    if (dist >= circle.Radius)
                    {
                        // Advance to next pixel
                        row--;
                        xRight--;
                        continue;
                    }
                    if (dist <= circle.Radius - sr)
                    {
                        break;
                    }

                    // Compute the alpha value
                    float newAFloat = (circle.Radius - dist) / sr * alpha * 255.0f;
                    // Blend
                    BlendComposite((clr & 0x00FFffFF) | (((uint)newAFloat) << 24), row);

                    // Advance to next pixel
                    row--;
                    xRight--;

                    if (xRight < xLeft)
                    {
                        break;
                    }
                }

                // At this point we've processed from the left and right edges of the circle
                // on this row and have taken care of all pixels that need edge softening. All
                // that remains is filling pixels in between where we left off on the left and
                // right.
                if (xRight >= xLeft)
                {
                    DrawHorizontalLine(xLeft, xRight, ystart, clr, img.Pixels, img.Width, img.Height,
                        BlendMode.AlphaComposite);
                }
            }
        }
Пример #4
0
        // TODO: Make this obey blending rules properly
        public unsafe void DrawPieChart(uint* pxls, int width, int height,
            PieChartSection[] sections, uint color)
        {
            if (0 == sections.Length)
            {
                throw new ArgumentException("Pie chart sections array cannot be empty");
            }

            // We need at least a 5x5 image
            if (width < 5 || height < 5)
            {
                return;
            }

            // If the image is not square, we want non-zero starting points
            int ystart = 0;
            int yend = height - 1; // Inclusive upper bound
            if (height > width)
            {
                ystart = (height - width) / 2;
                yend = height - ystart;
            }

            float xcenter = (float)width / 2.0f;
            float ycenter = (float)height / 2.0f;
            double radius = Math.Min(xcenter, ycenter) - 2.0;

            // Make a circle object for the pie chart area
            Circle2D circle = new Circle2D(xcenter, ycenter, (float)radius);

            // Compute the sum of all values in the pie sections
            double sum = 0.0;
            foreach (PieChartSection pcs in sections)
            {
                sum += pcs.Value;
            }

            // Make an angle table. This table stores the ending angle of each category. Also
            // make a color table.
            double[] angles = new double[sections.Length];
            uint[] clrLookup = new uint[sections.Length];
            int i = 0;
            double angle = 0.0;
            foreach (PieChartSection pcs in sections)
            {
                clrLookup[i] = pcs.Color;
                angles[i] = pcs.Value / sum * (Math.PI * 2.0) + angle;
                angle = pcs.Value / sum * (Math.PI * 2.0) + angle;
                i++;
            }

            while (ystart <= yend)
            {
                // Compute the intersections of the scan line with the pie chart
                float minx, maxx;
                circle.HLineIntersection((float)ystart, out minx, out maxx);

                // Clear the row if there isn't an intersection
                if (float.IsNaN(minx) || float.IsNaN(maxx))
                {
                    DrawHorizontalLine(0, width - 1, ystart, 0, pxls, width, height, BlendMode.None);
                    ystart++;
                    continue;
                }

                int xstart = (int)Math.Floor(minx);
                int xend = (int)Math.Min(Math.Ceiling(maxx), (float)(width - 1));
                uint* row = &pxls[ystart * width + xstart];
                for (int x = xstart; x <= xend; x++)
                {
                    // For each pixel, we want to start by computing the distance from the center
                    Vector2D pos = new Vector2D((float)x - xcenter, ycenter - (float)ystart);
                    double dist = pos.Length;

                    // Set the pixel to 0 if we're outside of the pie chart area
                    if (dist >= radius)
                    {
                        *row++ = 0;
                        continue;
                    }

                    // Compute the angle so we know what category of the pie we're in
                    angle = pos.GetCCWAngle();
                    i = 0;
                    while (angle > angles[i])
                    {
                        i++;
                    }

                    // Advance to the next pixel
                    *row++ = clrLookup[i];
                }

                ystart++;
            }

            // Draw lines between sections
            if (sections.Length > 1)
            {
                List<Stadium> stadiums = new List<Stadium>();
                foreach (double tempAngle in angles)
                {
                    stadiums.Add(new Stadium(circle.Center,
                            new Vector2D(xcenter + (float)(radius * Math.Cos(tempAngle)),
                            ycenter - (float)(radius * Math.Sin(tempAngle))), 1.75f));
                }
                FillStadiums(new SimpleImage(width, height, pxls), color, stadiums.ToArray(), 2f);
            }

            // Draw a circular border around the pie chart
            FrameCircle_Composite(circle, 3.5f, color, pxls, width, height);
        }
Пример #5
0
        /// <summary>
        /// Draws an arc using alpha-compositing as the blend mode. Currently uses a hard-coded softening 
        /// distance value of 1.5 pixels.
        /// </summary>
        /// <param name="startAngle">Start angle, in radians</param>
        /// <param name="sweepAngle">Sweep angle, in radians</param>
        public unsafe void DrawArc(Circle2D circle, float lineThickness, double startAngle, double sweepAngle,
            uint color, uint* pixels, int imageWidth, int imageHeight)
        {
            // Check parameters
            if (lineThickness <= 0.0f)
            {
                throw new ArgumentException(
                    "Line thickness value must be a positive number");
            }

            // If the circle is degenerate then we have nothing to render
            if (circle.IsDegenerate)
            {
                return;
            }

            // Special case: if the sweep angle is >= 2*pi then we just frame the whole circle
            if (sweepAngle >= c_twoPi)
            {
                FrameCircle(circle, lineThickness, color, pixels, imageWidth, imageHeight);
                return;
            }

            // Softening radius (hard-coded for now) (change method summary if this changes)
            float sr = 1.5f;

            // Compute lineThickness / 2 because it's used a lot
            float lto2 = lineThickness / 2.0f;

            double colorAlphaDouble = ((double)(color >> 24)) / 255.0;

            // Another special case if the line is thick enough to extend in and past the center of the circle
            if (circle.Radius - lto2 <= 0.0f)
            {
                FillCircle(new SimpleImage(imageWidth, imageHeight, pixels), color, circle);
                return;
            }

            // Build the "outer" circle
            Circle2D outer = new Circle2D(circle.Center, circle.Radius + lto2);

            // Build the "inner" circle
            Circle2D inner = new Circle2D(circle.Center, circle.Radius - lto2);

            // Build the arc object
            CircleArc ca = new CircleArc(circle, startAngle, sweepAngle);

            // Find out the starting y-value
            int ystart = (int)Math.Floor(ca.MinY - lto2);

            // If it's below the bottom of the image then we have nothing to render
            if (ystart >= imageHeight)
            {
                return;
            }

            // Make sure it's not < 0
            if (ystart < 0)
            {
                ystart = 0;
            }

            // Now determine the inclusive ending y-value
            int yend = (int)Math.Ceiling(ca.MaxY + lto2);
            if (yend < 0) {return;}
            if (yend >= imageHeight)
            {
                yend = imageHeight - 1;
            }

            // Loop through rows of pixels and render
            while (ystart++ <= yend)
            {
                // Compute outer circle intersections
                float ocx1, ocx2;
                outer.HLineIntersection((float)ystart, out ocx1, out ocx2);

                // If there's no intersection or the two x-values are equal, then move onto
                // the next y-value
                if (float.IsNaN(ocx1) || float.IsNaN(ocx2) || ocx1 == ocx2)
                {
                    continue;
                }

                // Compute the inner circle intersections
                float icx1, icx2;
                inner.HLineIntersection((float)ystart, out icx1, out icx2);

                float[] xvals;
                if (float.IsNaN(icx1) || float.IsNaN(icx2) || icx1 == icx2)
                {
                    // If there's no inner circle intersections then our array just has the two
                    // x-values for the outer intersection.
                    xvals = new float[] { ocx1, ocx2 };
                }
                else
                {
                    xvals = new float[] { ocx1, icx1, icx2, ocx2 };
                }

                // Render between pairs of intersection points
                for (int i = 0; i < xvals.Length; i+=2)
                {
                    int x1 = (int)Math.Floor(xvals[i]);
                    if (x1 >= imageWidth)
                    {
                        continue;
                    }
                    if (x1 < 0) { x1 = 0; }
                    int x2 = (int)Math.Ceiling(xvals[i + 1]);
                    if (x2 >= imageWidth) { x2 = imageWidth - 1; }

                    uint* start = &pixels[ystart * imageWidth + x1];
                    uint* end = &pixels[ystart * imageWidth + x2];
                    while (start++ <= end)
                    {
                        float dist = ca.DistanceU(new Vector2D((float)x1, (float)ystart));
                        if (dist >= lto2)
                        {
                            x1++;
                            continue;
                        }
                        else if (dist > (lto2 - sr))
                        {
                            double alpha = (lto2 - dist) / sr;
                            alpha *= colorAlphaDouble;
                            uint clr = color & 0x00FFffFF;
                            clr |= (((uint)(alpha * 255.0)) << 24);
                            BlendComposite(clr, start);
                        }
                        else
                        {
                            BlendComposite(color, start);
                        }

                        x1++;
                    }
                }
            }
        }
Пример #6
0
        protected unsafe void FrameCircle_Composite(Circle2D circle, float frameThickness,
            uint clr, uint* pxls, int w, int h)
        {
            // Softening radius (hard-coded for now)
            float sr = 2.0f;

            // Handle case where the frame is so thick that we just fill the whole circle
            if (frameThickness / 2.0f >= circle.Radius + sr)
            {
                Circle2D newCircle = new Circle2D(circle.Center, circle.Radius + frameThickness / 2f);
                FillCircle(new SimpleImage(w, h, pxls), clr, circle);
                return;
            }

            // First make the outer and inner circle objects
            Circle2D outer = new Circle2D(circle.Center, circle.Radius + frameThickness / 2.0f);
            Circle2D inner = new Circle2D(circle.Center, circle.Radius - frameThickness / 2.0f);

            int y = (int)Math.Max(0.0f, Math.Floor(outer.MinY));
            for (; y < h; y++)
            {
                // Compute the x-intersection points for this y-value
                float outMin, outMax, inMin, inMax;
                outer.HLineIntersection((float)y, out outMin, out outMax);
                inner.HLineIntersection((float)y, out inMin, out inMax);

                // If there's not a valid intersection, then skip this row. Note that there
                // could be an intersection with the outer circle and not the inner, but not
                // the other way around.
                if (float.IsNaN(outMin) || float.IsNaN(outMax))
                {
                    continue;
                }

                float[] ranges;
                if (float.IsNaN(inMin) || float.IsNaN(inMax))
                {
                    // This means there's an intersection with the outer circle and not the
                    // inner. In this case we only have one continuous range of pixels to
                    // process for this row.

                    // Check to see if we're off the image
                    if (outMax < 0.0f || outMin > (float)(w - 1))
                    {
                        continue;
                    }

                    ranges = new float[] { outMin, outMax };
                }
                else
                {
                    ranges = new float[] { outMin, inMin, inMax, outMax };
                }

                // Process the pixel ranges
                for (int i = 0; i < ranges.Length; i += 2)
                {
                    int x = (int)Math.Max(0.0f, Math.Floor(ranges[i]));
                    int maxx = (int)Math.Min((float)(w - 1), Math.Ceiling(ranges[i + 1]));

                    // Make a pointer to the pixel where we start rendering
                    uint* row = &pxls[y * w + x];

                    while (x <= maxx)
                    {
                        // Compute the distance from this pixel to the edge of the circle and
                        // soften if needed
                        float dist = circle.DistanceFromCenter(new Vector2D(x, y));
                        if (dist >= outer.Radius || dist <= inner.Radius)
                        {
                            x++;
                            row++;
                            continue;
                        }
                        if ((outer.Radius - dist) <= sr)
                        {
                            float newA = ((outer.Radius - dist) / sr) * 255.0f;
                            BlendComposite((clr & 0x00FFffFF) | (((uint)newA) << 24), row);
                        }
                        else if ((dist - inner.Radius) <= sr)
                        {
                            float newA = ((dist - inner.Radius) / sr) * 255.0f;
                            BlendComposite((clr & 0x00FFffFF) | (((uint)newA) << 24), row);
                        }
                        else
                        {
                            BlendComposite(clr, row);
                        }

                        x++;
                        row++;
                    }
                }
            }
        }
Пример #7
0
 public unsafe void FrameCircle(Circle2D circle, float frameThickness, uint clr,
     uint* pxls, int imgWidth, int imgHeight)
 {
     FrameCircle_Composite(circle, frameThickness, clr, pxls, imgWidth, imgHeight);
 }