private unsafe void DrawRoundLine_None(Stadium stad, uint clr, uint* pxls, int w, int h) { // Compute the inclusive maximum bound for Y int maxy = (int)Math.Ceiling(stad.MaxY); if (maxy > h - 1) { maxy = h - 1; } else if (maxy < 0) { // We're not even on the image and don't need to draw anything return; } // Fill pixels within the stadium for (int y = (int)Math.Max(Math.Floor(stad.MinY), 0.0); y <= maxy; y++) { float minx, maxx; stad.HLineIntersection((float)y, out minx, out maxx); // If there's no intersection, then we can move to the next row if (float.IsNaN(minx) || float.IsNaN(maxx)) { continue; } // Compute the inclusive upper and lower bounds for X for this row int x = (int)Math.Floor(minx); if (x < 0) { x = 0; } int rowMaxX = (int)Math.Ceiling(maxx); if (rowMaxX > w - 1) { rowMaxX = w - 1; } // Get a pointer to the row of pixels uint* rowPtr = &pxls[y * w + x]; while (x <= rowMaxX) { float dist = stad.Axis.DistanceU(new Vector2D(x, y)); if (dist >= stad.Radius) { x++; rowPtr++; continue; } float distFromEdge = stad.Radius - dist; if (distFromEdge < 2.0f) { // Soften alpha value uint newa = (uint)((distFromEdge / 2.0f) * (float)((*rowPtr) >> 24)); *rowPtr = (clr & 0x00FFffFF) | (newa << 24); } else { *rowPtr = clr; } x++; rowPtr++; } } }
private unsafe void FillStadiums(SimpleImage img, uint clr, Stadium[] stads, float sr) { float clrAlphaFloat = (float)(clr >> 24) / 255f; // Find the start and end (inclusive) Y-values float minyFloat = float.MaxValue, maxyFloat = float.MinValue; foreach (Stadium sTemp in stads) { if (sTemp.MaxY > maxyFloat) { maxyFloat = sTemp.MaxY; } if (sTemp.MinY < minyFloat) { minyFloat = sTemp.MinY; } } int miny = (int)Math.Floor(minyFloat); if (miny >= img.Height) { // Content is completely off the image return; } miny = Math.Max(miny, 0); int maxy = Math.Min((int)Math.Ceiling(maxyFloat), img.Height - 1); // We need lists to keep track of minimum and maximum intersections per row List<float> mins = new List<float>(); List<float> maxes = new List<float>(); List<float> finals = new List<float>(); // Loop through rows of pixels while (miny <= maxy) { // Clear intersection lists mins.Clear(); maxes.Clear(); finals.Clear(); // Get all intersections for this row foreach (Stadium sTemp in stads) { float tempMin, tempMax; sTemp.HLineIntersection((float)miny, out tempMin, out tempMax); if (!float.IsNaN(tempMin) && !float.IsNaN(tempMax)) { mins.Add(tempMin); maxes.Add(tempMax); } } if (0 == mins.Count || 0 == maxes.Count) { miny++; continue; } // Sort the intersection lists mins.Sort(); maxes.Sort(); // Debug: plot a pixel at each intersection point //foreach (float f in mins) //{ // *img.GetRowAtX(miny, (int)f) = 0xFFFF0000; //} //foreach (float f in maxes) //{ // *img.GetRowAtX(miny, (int)f) = 0xFF00FF00; //} bool rowIsComplex = false; // When > 0, we're inside, otherwise we're outside the object. This gets incremented // when we hit a value in the "mins" list and decremented when we hit a value in // the "maxes" list. int in_out = 0; while (mins.Count > 0 || maxes.Count > 0) { if (0 == mins.Count) { finals.Add(maxes[maxes.Count - 1]); break; } else if (0 == maxes.Count) { // Should never occur break; } if (mins[0] < maxes[0]) { if (0 == in_out) { // 0 == in_out implies that we were outside and now we're entering finals.Add(mins[0]); } else { // This means that in_out is > 0 and we've entered 2 stadiums (or // more) at once. This implies a "complex" row, which gets rendered // without certain optimizations. rowIsComplex = true; } mins.RemoveAt(0); in_out++; } else { in_out--; if (0 == in_out) { // This is an exit point finals.Add(maxes[0]); } maxes.RemoveAt(0); } } int xstart, xend; // The "finals" list now contains pairs of enter-exit x-values for (int i = 0; i < finals.Count - 1; i += 2) { xstart = Math.Max((int)Math.Floor(finals[i]), 0); if (xstart >= img.Width) { continue; } xend = Math.Min((int)Math.Ceiling(finals[i + 1]), img.Width - 1); if (xend < 0) { continue; } uint* row = &img.Pixels[miny * img.Width + xstart]; // Start by coming in from the left edge until we are no longer in the edge // softening section while (xstart <= xend) { float minDist = float.MaxValue; Stadium minDistStadium = stads[0]; foreach (Stadium stadium in stads) { float dist = stadium.Axis.DistanceU(new Vector2D((float)xstart, (float)miny)); if (dist < minDist) { minDist = dist; minDistStadium = stadium; } } if (minDist < minDistStadium.Radius) { if (minDist > minDistStadium.Radius - sr) { float alpha = (minDistStadium.Radius - minDist) / sr; alpha *= clrAlphaFloat; uint newAlpha = (uint)(alpha * 255f); newAlpha <<= 24; BlendComposite((clr & 0x00FFffFF) | newAlpha, row); } else { if (rowIsComplex) { BlendComposite(clr, row); } else { break; } } } xstart++; row++; } // It's possible that we completed this section in the above loop if (xstart > xend) { continue; } // Get a pointer to pixels on the right edge row = img.GetRowAtX(miny, xend); // Now come in from the right edge until we are no longer in the edge // softening section while (xend >= xstart) { float minDist = float.MaxValue; Stadium minDistStadium = stads[0]; foreach (Stadium stadium in stads) { float dist = stadium.Axis.DistanceU(new Vector2D((float)xend, (float)miny)); if (dist < minDist) { minDist = dist; minDistStadium = stadium; } } if (minDist < minDistStadium.Radius) { if (minDist > minDistStadium.Radius - sr) { float alpha = (minDistStadium.Radius - minDist) / sr; alpha *= clrAlphaFloat; uint newAlpha = (uint)(alpha * 255f); newAlpha <<= 24; BlendComposite((clr & 0x00FFffFF) | newAlpha, row); } else { break; } } xend--; row--; } // If there are remaining pixels between xstart and xend then render them if (xstart <= xend) { DrawHorizontalLine(xstart, xend, miny, clr, img.Pixels, img.Width, img.Height, BlendMode.AlphaComposite); } } miny++; } }
private unsafe void DrawRoundLine_Composite(Stadium stad, uint clr, uint* pxls, int w, int h) { FillStadiums(new SimpleImage(w, h, pxls), clr, new Stadium[] { stad }, 2f); }