views:

604

answers:

2

I have been considering using the code example shown at Shell Style Drag and Drop in .NET - Part 3 within a WPF project. The sample project works fine, it is a great article so check it out!

However when moving the code to my project I receive an error when compiling "Unsafe code may only appear if compiling with /unsafe" I understand that I could just change the compile options, however I would prefer to not have the unsafe code in there. The unsafe code is as follows.

How can I make this code safe? I don't really have experience in this field.

/// <summary>
/// Replaces any pixel with a zero alpha value with the specified transparency key.
/// </summary>
/// <param name="bmpData">The bitmap data in which to perform the operation.</param>
/// <param name="transKey">The transparency color. This color is rendered transparent
/// by the DragDropHelper.</param>
/// <remarks>
/// This function only supports 32-bit pixel formats for now.
/// </remarks>
private static void ReplaceTransparentPixelsWithTransparentKey(BitmapData bmpData, DrawingColor transKey)
{
 DrawingPixelFormat pxFormat = bmpData.PixelFormat;

 if (DrawingPixelFormat.Format32bppArgb == pxFormat
  || DrawingPixelFormat.Format32bppPArgb == pxFormat)
 {
  int transKeyArgb = transKey.ToArgb();

  // We will just iterate over the data... we don't care about pixel location,
  // just that every pixel is checked.
  unsafe
  {
   byte* pscan = (byte*)bmpData.Scan0.ToPointer();
   {
    for (int y = 0; y < bmpData.Height; ++y, pscan += bmpData.Stride)
    {
     int* prgb = (int*)pscan;
     for (int x = 0; x < bmpData.Width; ++x, ++prgb)
     {
      // If the alpha value is zero, replace this pixel's color
      // with the transparency key.
      if ((*prgb & 0xFF000000L) == 0L)
       *prgb = transKeyArgb;
     }
    }
   }
  }
 }
 else
 {
  // If it is anything else, we aren't supporting it, but we
  // won't throw, cause it isn't an error
  System.Diagnostics.Trace.TraceWarning("Not converting transparent colors to transparency key.");
  return;
 }
}

The function calling this code is as follows, maybe the ReplaceTransparentPixelsWithTransparentKey function could be removed entirely by another method. Any ideas?

/// <summary>
/// Gets a System.Drawing.Bitmap from a BitmapSource.
/// </summary>
/// <param name="source">The source image from which to create our Bitmap.</param>
/// <param name="transparencyKey">The transparency key. This is used by the DragDropHelper
/// in rendering transparent pixels.</param>
/// <returns>An instance of Bitmap which is a copy of the BitmapSource's image.</returns>
private static Bitmap GetBitmapFromBitmapSource(BitmapSource source, Color transparencyKey)
{
 // Copy at full size
 Int32Rect sourceRect = new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight);

 // Convert to our destination pixel format
 DrawingPixelFormat pxFormat = ConvertPixelFormat(source.Format);

 // Create the Bitmap, full size, full rez
 Bitmap bmp = new Bitmap(sourceRect.Width, sourceRect.Height, pxFormat);
 // If the format is an indexed format, copy the color palette
 if ((pxFormat & DrawingPixelFormat.Indexed) == DrawingPixelFormat.Indexed)
  ConvertColorPalette(bmp.Palette, source.Palette);

 // Get the transparency key as a System.Drawing.Color
 DrawingColor transKey = transparencyKey.ToDrawingColor();

 // Lock our Bitmap bits, we need to write to it
 BitmapData bmpData = bmp.LockBits(
  sourceRect.ToDrawingRectangle(),
  ImageLockMode.ReadWrite,
  pxFormat);
 {
  // Copy the source bitmap data to our new Bitmap
  source.CopyPixels(sourceRect, bmpData.Scan0, bmpData.Stride * sourceRect.Height, bmpData.Stride);

  // The drag image seems to work in full 32-bit color, except when
  // alpha equals zero. Then it renders those pixels at black. So
  // we make a pass and set all those pixels to the transparency key
  // color. This is only implemented for 32-bit pixel colors for now.
  if ((pxFormat & DrawingPixelFormat.Alpha) == DrawingPixelFormat.Alpha)
   ReplaceTransparentPixelsWithTransparentKey(bmpData, transKey);
 }
 // Done, unlock the bits
 bmp.UnlockBits(bmpData);

 return bmp;
}
+1  A: 

If you don't want a significant performance hit, then you don't really have any other options.

You really shouldn't be scared of unsafe keyword and the associated compiler switch, either - I often see people trying to find some workaround that is still unsafe (e.g. using Marshal, or Win32 API), but doesn't require unsafe keyword. There's no point in that - if anything, it's more harmful, because unsafe stands out.

In this case, for example, you could of course move the entire pointer arithmetic part into a C DLL or C++/CLI assembly, and invoke that from C# directly or via P/Invoke. But what would be the point?

Pavel Minaev
+1  A: 

Nothing about the drag and drop is unsafe, it's the pointer manipulation in the image processing code. Rather than coding this yourself, why not use one of the existing methods in the framework?

  • Use System.Drawing.Imaging SetRemapTable (example here) to replace the transparency via a color map.

  • See if you can handle this with pixel shaders, which would result in safe code an would likely perform a lot better because it would leverage the GPU.

Jon Galloway
Thanks! I am dealing with the BitmapData here so I don't quite understand how I would use the ColorMap when the function is not drawing the bitmap.
Luke
Can you give more context of what you're doing? Are you dropping image files into an application and doing graphic manipulationon them?
Jon Galloway
This is for Shell drag and drop (full code within the above article). The above function ReplaceTransparentPixelsWithTransparentKey to resolve: The drag image seems to work in full 32-bit color, except when alpha equals zero. Then it renders those pixels at black. So we make a pass and set all those pixels to the transparency key color. This is only implemented for 32-bit pixel colors for now.
Luke
Jon, I have added more source code where this function is being called. Maybe the ReplaceTransparentPixelsWithTransparentKey could be removed?
Luke
I'd try removing that call and test with a transparent image. If it's needed, you've got several options: 1. Leave the unsafe code. 2. Iterate through the pixels in managed code (slow but managed). 3. Write a ShaderEffect (very fast but some work http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/bb81d488-5b9b-4737-8b74-1476f843a717). 4. Figure out how to load the BitmapSource into an Image, which would let you use SetRemapTable.
Jon Galloway