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