0

Have two pointers (byte*) to 1. B8G8R8A8 pixel data 2. byte buffer to put cropped pixel data, very simple issue. Here is my implementation:

private unsafe void Crop(byte* src, byte* dst, Rectangle rSrc, Rectangle rDst)
{
    int sSrc = 4 * rSrc.Width, sDst = 4 * rDst.Width; // stride

    for (int x = 0; x < rDst.Width; x++) for (int y = 0; y < rDst.Height; y++)
    {
        dst[(x * 4 + y * (sDst)) + 0] = src[((rDst.X + x) * 4 + (rDst.Y + y) * (sSrc)) + 0];
        dst[(x * 4 + y * (sDst)) + 1] = src[((rDst.X + x) * 4 + (rDst.Y + y) * (sSrc)) + 1];
        dst[(x * 4 + y * (sDst)) + 2] = src[((rDst.X + x) * 4 + (rDst.Y + y) * (sSrc)) + 2];
        dst[(x * 4 + y * (sDst)) + 3] = src[((rDst.X + x) * 4 + (rDst.Y + y) * (sSrc)) + 3];
    }
}

And it works: ~ 100ms for 1920x1080, but need ~ 10 - 15 ms. Is it possible to make it faster? For example plain copy (without crop) provides much better performance 8 ms (for the same resolution), here is function:

private unsafe void Copy(void* dst, void* src, long count)
{
    long block; block = count >> 3;

    long* pDst = (long*)dst; long* pSrc = (long*)src;
    {
       for (int i = 0; i < block; i++) { *pDst = *pSrc; pDst++; pSrc++; }
    }
}

Need your thoughts!

Thanks.

  • 1
    Have you tried the .NET framework cropping methods, creating a new bitmap from an existing one and a rectangle? It may not perform as badly as you think. Otherwise there are unmanaged methods for bit blitting that may be faster by copying an entire row at once instead of pixel-by-pixel-by-byte. – Ron Beyer Sep 28 '15 at 16:00
  • Yes. it is very possible to make it faster. Precompute what you can in your loops, and find a way to get .NET to use some of the SIMD instructions. – Robert McKee Sep 28 '15 at 16:06
  • store `((rDst.X + x) * 4 + (rDst.Y + y) * (sSrc))` in variable and use it 4 times instead of calculating it 4 times. the same thing for `(x * 4 + y * (sDst))` – M.kazem Akhgary Sep 28 '15 at 16:10
  • What version of .NET Framework are you targeting? – Ivan Stoev Sep 28 '15 at 17:03
  • @IvanStoev 4.5 is target – Kirill Karahainko Sep 28 '15 at 17:10

2 Answers2

0

Actually you can make it run in in 3 milliseconds and don't need to be unsafe.

void Main()
{
    int width;
    int height;
    int border;

    byte[] src;
    using (var bm = new Bitmap(@"C:\Data\sample-photo.jpg"))
    {
        width = bm.Width;
        height = bm.Height;
        border = width / 10;

        src = new byte[width * height * 4];

        for (var y = 0; y < bm.Height; y++)
        for (var x = 0; x < bm.Width; x++)
        {
            var pixel = bm.GetPixel(x,y);
            var i = GetIndex(width, x,y);
            src[i + 1] = pixel.R;
            src[i + 2] = pixel.G;
            src[i + 3] = pixel.B;
        }
    }   

    var sw = Stopwatch.StartNew();
    byte[] dst = null;

    dst = Crop(src, width, border, width - border, border, height - border);
    sw.Stop();

    Console.WriteLine(sw.Elapsed);

    using (var dstBmp = new Bitmap(width - border * 2, height - border * 2, PixelFormat.Format32bppArgb))
    {
        for (var y = 0; y < (height - border * 2); y++)
            for (var x = 0; x < (width - border * 2); x++)
            {
                var i = GetIndex(dstBmp.Width, x, y);
                var r = dst[i + 1];
                var g = dst[i + 2];
                var b = dst[i + 3];
                dstBmp.SetPixel(x, y, Color.FromArgb(255, r, g, b));
            }
        dstBmp.Save(@"C:\Data\sample-photo-cropped.jpg");
    }
}

byte[] Crop(byte[] src, int srcWidth, int xStart, int xEnd, int yStart, int yEnd)
{
    var width = xEnd - xStart;
    var height = yEnd - yStart;

    var dst = new byte[width * height * 4];

    for (var iY = yStart; iY < yEnd; iY++)
    {
        Array.Copy(src, GetIndex(srcWidth, xStart, iY), dst, GetIndex(width, 0, iY - yStart), width * 4);
    }

    return dst;
}

int GetIndex(int width, int x, int y)
{
    return (x + (width * y)) * 4;
}

Output:

00:00:00.0007131

To work with pointers you can use Marshal class, which has a bunch of overloads for copying memory or the Buffer.MemoryCopy Method

George Polevoy
  • 7,450
  • 3
  • 36
  • 61
  • Thanks for quick reply! To be "unsafe" (pointers) is a limitation of an external system, I'm getting "images" from a device and need to apply some transformations and encode mp4. Sure its possible to get an array via IntPtr and then apply crop but not sure its a good idea. – Kirill Karahainko Sep 28 '15 at 16:50
  • Anyway fixed the code for a more complete example and fixed the errors. Now it actually crops a bitmap in around a millisecond time. – George Polevoy Sep 28 '15 at 16:58
  • This is a great example. I would optimize it further by using bit shifts instead of multiplication, and skip the calls to GetIndex in the loop for the destination address since it will always be consecutive, and skip the calls to GetIndex in the loop and preinit it outside the loop and use add to skip the unused bytes. – Robert McKee Sep 28 '15 at 17:28
  • Nice example, thanks. But I don't think any Marshal overload or Buffer can be used to replace Array.Copy. – Kirill Karahainko Sep 28 '15 at 18:52
0

There you go

private static unsafe void Crop(byte* src, byte* dst, Rectangle rSrc, Rectangle rDst)
{
    int* pDst = (int*)dst, pSrc = (int*)src + (rDst.Y * rSrc.Width + rDst.X);
    int srcSkip = rSrc.Width - rDst.Width;
    int rowCount = rDst.Height, colCount = rDst.Width;
    for (int row = 0; row < rowCount; row++)
    {
        for (int col = 0; col < colCount; col++) *pDst++ = *pSrc++;
        pSrc += srcSkip;
    }
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343