Graphics.DrawImage alternatives for large images

2019-04-10 21:03发布

I am trying to draw a crosshair ("plus sign") with inverted colors over an image to show the location of a selected point within the image. This is how I do it:

private static void DrawInvertedCrosshair(Graphics g, Image img, PointF location, float length, float width)
{
    float halfLength = length / 2f;
    float halfWidth = width / 2f;

    Rectangle absHorizRect = Rectangle.Round(new RectangleF(location.X - halfLength, location.Y - halfWidth, length, width));
    Rectangle absVertRect = Rectangle.Round(new RectangleF(location.X - halfWidth, location.Y - halfLength, width, length));

    ImageAttributes attributes = new ImageAttributes();
    float[][] invertMatrix =
    { 
        new float[] {-1,  0,  0,  0,  0 },
        new float[] { 0, -1,  0,  0,  0 },
        new float[] { 0,  0, -1,  0,  0 },
        new float[] { 0,  0,  0,  1,  0 },
        new float[] { 1,  1,  1,  0,  1 }
    };
    ColorMatrix matrix = new ColorMatrix(invertMatrix);
    attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

    g.DrawImage(img, absHorizRect, absHorizRect.X, absHorizRect.Y, absHorizRect.Width, absHorizRect.Height, GraphicsUnit.Pixel, attributes);
    g.DrawImage(img, absVertRect, absVertRect.X, absVertRect.Y, absVertRect.Width, absVertRect.Height, GraphicsUnit.Pixel, attributes);
}

It works as expected, however, it is really slow. I want the user to be able to move the selected location around with their mouse by setting the location to the cursor's location whenever it moves. Unfortunately, on my computer, it can update only around once per second for big images.

So, I am looking for an alternative to using Graphics.DrawImage to invert a region of an image. Are there any ways to do this with speeds proportional to the selected region area rather than the entire image area?

标签: c# .net drawing
2条回答
闹够了就滚
2楼-- · 2019-04-10 22:03

Draw the image once on Graphics g, then draw the crosshair on Graphics g directly instead of the image. You can optionally keep track of the places the user clicked so as to save them either in the image or elsewhere as needed.

查看更多
淡お忘
3楼-- · 2019-04-10 22:06

Sounds to me you are focusing on the wrong problem. Painting the image is slow, not painting the "cross-hairs".

Large images can certainly be very expensive when you don't help. And System.Drawing makes it very easy to not help. Two basic things you want to do to make the image paint faster, getting it more than 20 times faster is quite achievable:

  • avoid forcing the image painting code to rescale the image. Instead do it just once so the image can be drawn directly one-to-one without any rescaling. Best time to do so is when you load the image. Possibly again in the control's Resize event handler.

  • pay attention to the pixel format of the image. The fastest one by a long shot is the pixel format that's directly compatible with the way the image needs to be stored in the video adapter. So the image data can be directly copied to video RAM without having to adjust each individual pixel. That format is PixelFormat.Format32bppPArgb on 99% of all modern machines. Makes a huge difference, it is ten times faster than all the other ones.

A simple helper method that accomplishes both without otherwise dealing with the aspect ratio:

private static Bitmap Resample(Image img, Size size) {
    var bmp = new Bitmap(size.Width, size.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
    using (var gr = Graphics.FromImage(bmp)) {
        gr.DrawImage(img, new Rectangle(Point.Empty, size));
    }
    return bmp;
}
查看更多
登录 后发表回答