6
\$\begingroup\$

I am making a GIF editor in C#, and I am currently working on the eraser tool. I am not sure what I can do to make it more efficient, because when I make the thickness (diameter) more than 30 (pixels) it starts to get a bit slow.

if (click && eraserSelected)
        {
            new Thread(() =>
            {
                Application.Current.Dispatcher.Invoke((Action)(() => b = imageBox.Source as WriteableBitmap));
                Application.Current.Dispatcher.Invoke((Action)(() => endPoint = Mouse.GetPosition(imageBox)));

                System.Windows.Media.Color color;
                System.Windows.Point[] linePixels = FindPixels.Line(startPoint, endPoint).ToArray();
                int[][][] pixels = new int[linePixels.Length][][];

                for (int i = 0; i < linePixels.Length; i++)
                    pixels[i] = FindPixels.Circle(thickness, (int)linePixels[i].X, (int)linePixels[i].Y);

                Bitmap originalFrame = gifFrames.GetOriginalFrame();

                Application.Current.Dispatcher.Invoke((() =>
                {
                    for (int j = 0; j < pixels.Length; j++)
                        for (int i = 0; i < pixels[j][0].Length; i++)
                            if (pixels[j][0][i] < pixelsEdited[0].Count() && pixels[j][0][i] >= 0 && pixels[j][1][i] < pixelsEdited.Count() && pixels[j][1][i] >= 0)
                                if (!pixelsEdited[pixels[j][1][i]][pixels[j][0][i]])
                                {
                                    color = System.Windows.Media.Color.FromRgb(originalFrame.GetPixel(pixels[j][0][i], pixels[j][1][i]).R, originalFrame.GetPixel(pixels[j][0][i], pixels[j][1][i]).G, originalFrame.GetPixel(pixels[j][0][i], pixels[j][1][i]).B);
                                    try
                                    {
                                        b.SetPixel(pixels[j][0][i], pixels[j][1][i], color);
                                        pixelsEdited[pixels[j][1][i]][pixels[j][0][i]] = true;
                                    }
                                    catch { }
                                }
                }));

                Application.Current.Dispatcher.Invoke((Action)(() => startPoint = endPoint));
            }).Start();
        }

That is the code for the eraser tool itself. Here is the code for the FindPixels.Line() and FindPixels.Circle() functions:

class FindPixels
{
    /// <summary>
    /// Finds all the pixels that are within a circle of a given diameter, and center
    /// </summary>
    /// <param name="diameter"></param>
    /// <param name="centerX"></param>
    /// <param name="centerY"></param>
    /// <returns>All the pixels within a circle</returns>
    public static int[][] Circle(int diameter, int centerX, int centerY)
    {
        int radius = diameter / 2;
        List<int> pixelsX = new List<int>();
        List<int> pixelsY = new List<int>();

        for (int x = centerX - radius; x < centerX + radius; x++)
        {
            for (int y = centerY - radius; y < centerY + radius; y++)
            {
                double dx = x - centerX;
                double dy = y - centerY;
                double distanceSquared = dx * dx + dy * dy;

                if (distanceSquared <= radius * radius)
                {
                    int distance = y - (centerY - radius);
                    for (int i = 0; i < diameter - distance * 2; i++)
                    {
                        pixelsX.Add(x);
                        pixelsY.Add(y + i);
                    }
                    break;
                }
            }
        }

        int[][] pixels = { pixelsX.ToArray(), pixelsY.ToArray() };
        return pixels;
    }

    /// <summary>
    /// Finds all pixels that form a line
    /// </summary>
    /// <param name="startPoint"></param>
    /// <param name="endPoint"></param>
    /// <returns>All points within the two points given</returns>
    public static List<Point> Line(Point p0, Point p1)
    {
        int x0 = (int)p0.X;
        int y0 = (int)p0.Y;
        int x1 = (int)p1.X;
        int y1 = (int)p1.Y;
        int dx = Math.Abs(x1 - x0);
        int dy = Math.Abs(y1 - y0);

        int sx = x0 < x1 ? 1 : -1;
        int sy = y0 < y1 ? 1 : -1;

        int err = dx - dy;

        var points = new List<Point>();

        while (true)
        {
            points.Add(new Point(x0, y0));
            if (x0 == x1 && y0 == y1) break;

            int e2 = 2 * err;
            if (e2 > -dy)
            {
                err = err - dy;
                x0 = x0 + sx;
            }
            if (e2 < dx)
            {
                err = err + dx;
                y0 = y0 + sy;
            }
        }

        return points;
    }
}

What I was thinking was instead of drawing a bunch of circles on each pixel of the line, I draw the start and end point and then do a rectangle to connect them, but how would I find the points connecting them.

Here is a picture of what I mean:

enter image description here

The red points are what I am having trouble finding, and all the points withing the pink outline are what I would change.

\$\endgroup\$
  • 1
    \$\begingroup\$ Nice drawing! If that method is indeed more efficient and a reviewer happens to suggest it and even give you some code for it, that's great - but by asking on this site you're asking for feedback on any & all aspects of the code you're posting, not the code you would have liked to write and put up for review. Does your code work as intended? \$\endgroup\$ – Mathieu Guindon Apr 4 '16 at 2:14
1
\$\begingroup\$

It looks like your code is inefficient because you are trying to do a lot of low-level work yourselves instead of relying on existing functionality available in .NET framework, which is most likely optimized for low-level work. Here are the classes/methods I would try:

  • System.Drawing.Graphics.FromImage(bitmap) method to create a Graphics object which provides methods to draw on a bitmap;
  • System.Drawing.Drawing2D.GraphicsPath object to define the shape which you drawn in your question;
  • System.Drawing.Graphics.FillPath(Brush, GraphicsPath) to fill the shape needed.

Defining the shape drawn is a pretty simple trigonometric exercise: you have coordinates of your starting and ending circles (x1,y1 and x2,y2), and a bunch of similar right triangles. Here is the calculation of your points:

var alpha = Math.Atan2(startPoint.Y - endPoint.Y, endPoint.X - startPoint.X); //measuring angle from X axis in clockwise direction

var shift = new Vector(radius * Math.Sin(alpha), -radius * Math.Cos(alpha));
var points = new[] //points specified on drawing
{
    startPoint + shift,
    startPoint - shift,
    endPoint + shift,
    endPoint - shift
};

What you need to complete is to define a GraphicsPath which consists of 2 180-degree arcs and 2 lines, and pass it to Graphics.FillPath method

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.