3

What is the purpose of ImageLockMode in Bitmap.LockBits? For ReadOnly the documentation only states that

ReadOnly: Specifies that a portion of the image is locked for reading.

But the following code proves, that this is not true. I know the question has been asked before, this time I try with some actual code as I couldn't find an answer anywhere else.

If I run the following code, it behaves exactly as explained in the answer.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace LockBits_Trials
{
   class Program
   {
      static readonly Random rnd = new Random(42);
      static void Main(string[] args)
      {
         Bitmap bmp_fromFile = new Bitmap("example.png");
         Bitmap bmp_fromCtor = new Bitmap(100, 100, PixelFormat.Format24bppRgb);
         marshalCopy(bmp_fromFile, "result_marshalCopy_fromFile.png");
         marshalCopy(bmp_fromCtor, "result_marshalCopy_fromCtor.png");
         usePointer(bmp_fromFile, "result_usePointer_fromFile.png");
         usePointer(bmp_fromCtor, "result_usePointer_fromCtor.png");
      }

      private static unsafe void usePointer(Bitmap bmp, string filename)
      {
         ImageLockMode mode = ImageLockMode.ReadOnly;
         //code from turgay at http://csharpexamples.com/fast-image-processing-c/ 
         if (bmp.PixelFormat != PixelFormat.Format24bppRgb)
            throw new Exception();
         BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), mode, bmp.PixelFormat);
         int bytesPerPixel = 3; int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel;
         byte* ptrFirstPixel = (byte*)bitmapData.Scan0;
         for (int y = 0; y < heightInPixels; y++) {
            byte* currentLine = ptrFirstPixel + (y * bitmapData.Stride);
            for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) {
               currentLine[x] = (byte)rnd.Next(0, 255);
               currentLine[x + 1] = (byte)rnd.Next(0, 255);
               currentLine[x + 2] = (byte)rnd.Next(0, 255);
            }
         }
         bmp.UnlockBits(bitmapData);
         bmp.Save(filename, ImageFormat.Png);
      }

      private static unsafe void marshalCopy(Bitmap bmp, string filename)
      {
         ImageLockMode mode = ImageLockMode.ReadOnly;
         if (bmp.PixelFormat != PixelFormat.Format24bppRgb)
            throw new Exception();
         BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), mode, bmp.PixelFormat);
         IntPtr ptrFirstPixel = bitmapData.Scan0;
         int totalBytes = bitmapData.Stride * bitmapData.Height;
         byte[] newData = new byte[totalBytes];
         for (int i = 0; i < totalBytes; i++)
            newData[i] = (byte)rnd.Next(0, 255);
         Marshal.Copy(newData, 0, ptrFirstPixel, newData.Length);
         bmp.UnlockBits(bitmapData);
         bmp.Save(filename, ImageFormat.Png);
      }
   }
}

The pictures result_marshalCopy_fromFile.png and result_usePointer_fromFile.png both contain the original image, so nothing was overwritten (and no Exception thrown!). The other two pictures contain the random noise, that was written to them while being locked.

I did not do further test to confirm the behaviour of parallel write access, as I don't do that anyways.

It's not a duplicate, but strongly related: Does Bitmap.LockBits “pin” a bitmap into memory?

Community
  • 1
  • 1
Xan-Kun Clark-Davis
  • 2,664
  • 2
  • 27
  • 38

1 Answers1

5

As you've observed, once you have gained access to the raw data pointer, there's nothing to stop you from writing to that memory, regardless of the lock type you requested. The lock type is only really useful in 2 situations:

1) If multiple bits of code request locks simultaneously, only one write lock is allowed to be issued at a time, while read locks can be shared. This, of course, depends on the code acquiring the locks using them appropriately.

2) Not all locks are directly backed by the Bitmap memory. In your example, it is because you created a memory Bitmap and then requested a lock in the same pixel format. However a bitmap may represent other GDI+ objects, such as pixels owned by a device context. Additionally, if you request a pixel format other than that of the source, it may have to be converted. In either of those cases, when a read lock is requested, GDI+ may have to pull a copy of the bitmap from the true source and give it to you in your requested pixel format. If you were to modify that copy, it would not get written back to the source context. If you requested a write lock, GDI+ would know to copy the pixels back to the source once you release the lock.

saucecontrol
  • 1,446
  • 15
  • 17
  • Thank you for the detailed answer. I just did some test that confirm what you say and edited my Question accordingly. If you could have a look over the code for errors? That would be awesome. – Xan-Kun Clark-Davis May 17 '17 at 03:15