I want my WPF application to be a drop target, and I want to be able to drag an image from any web page.
When an image is dragged from a web page, apparently it is i
Here's what I've learnt:
"DragImageBits" is provided by the windows shell, and is meant only for the drag cursor, not for the final data. The shell transforms the image to an appropriate drag cursor through resizing and transparency.
For example, if you drag this image:
The SHDRAGIMAGE will render as this:
If you really want to extract the image from the SHDRAGIMAGE, here is the code. (Partially lifted from this answer)
MemoryStream imageStream = data.GetData("DragImageBits") as MemoryStream;
imageStream.Seek(0, SeekOrigin.Begin);
BinaryReader br = new BinaryReader(imageStream);
ShDragImage shDragImage;
shDragImage.sizeDragImage.cx = br.ReadInt32();
shDragImage.sizeDragImage.cy = br.ReadInt32();
shDragImage.ptOffset.x = br.ReadInt32();
shDragImage.ptOffset.y = br.ReadInt32();
shDragImage.hbmpDragImage = new IntPtr(br.ReadInt32()); // I do not know what this is for!
shDragImage.crColorKey = br.ReadInt32();
int stride = shDragImage.sizeDragImage.cx * 4;
var imageData = new byte[stride * shDragImage.sizeDragImage.cy];
// We must read the image data as a loop, so it's in a flipped format
for (int i = (shDragImage.sizeDragImage.cy - 1) * stride; i >= 0; i -= stride)
{
br.Read(imageData, i, stride);
}
var bitmapSource = BitmapSource.Create(shDragImage.sizeDragImage.cx, shDragImage.sizeDragImage.cy,
96, 96,
PixelFormats.Bgra32,
null,
imageData,
stride);
If you want to utilize the DragImageBits for it's intended purpose (as a drag image), see Shell Style Drag and Drop in .NET (WPF and WinForms) (archived here) for a simple, downloadable example.
Dragging an image from a web page gets complicated, because Firefox, Chrome, and IE9 all give you a different set of formats. Also, you want to handle both an image and an image hyperlink, and these are treated differently again.
Google and Firefox provides a "text/html" format, which gives you a single HTML element as an image. Google gives it to you as an ASCII string, and Firefox gives it to you as a unicode string. So here's the code I wrote to handle it:
System.Windows.IDataObject data = e.Data;
string[] formats = data.GetFormats();
if (formats.Contains("text/html"))
{
var obj = data.GetData("text/html");
string html = string.Empty;
if (obj is string)
{
html = (string)obj;
}
else if (obj is MemoryStream)
{
MemoryStream ms = (MemoryStream)obj;
byte[] buffer = new byte[ms.Length];
ms.Read(buffer, 0, (int)ms.Length);
if (buffer[1] == (byte)0) // Detecting unicode
{
html = System.Text.Encoding.Unicode.GetString(buffer);
}
else
{
html = System.Text.Encoding.ASCII.GetString(buffer);
}
}
// Using a regex to parse HTML, but JUST FOR THIS EXAMPLE :-)
var match = new Regex(@"
In this case, the regular expression will handle both a straight image and an image hyperlink.
And my SetImageFromUri
function:
private void SetImageFromUri(Uri uri)
{
string fileName = System.IO.Path.GetTempFileName();
using (WebClient webClient = new WebClient())
{
webClient.DownloadFile(uri, fileName);
}
using (FileStream fs = File.OpenRead(fileName))
{
byte[] imageData = new byte[fs.Length];
fs.Read(imageData, 0, (int)fs.Length);
this.ImageBinary = imageData;
}
File.Delete(fileName);
}
For IE9 you can handle the "FileDrop" format. This works well in IE9. Chrome does not support it. Firefox does support it, but converts the image to a bitmap and converts transparent pixels to black. For this reason, you should only handle the "FileDrop" format if the "text.html" format isn't available.
else if (formats.Contains("FileDrop"))
{
var filePaths = (string[])data.GetData("FileDrop");
using (var fileStream = File.OpenRead(filePaths[0]))
{
var buffer = new byte[fileStream.Length];
fileStream.Read(buffer, 0, (int)fileStream.Length);
this.ImageBinary = buffer;
}
}
The "FileDrop" format is not provided if you drag an image hyperlink from IE9. I haven't figured out how to drag an image from an image hyperlink in IE9 onto my image control.
If you're using this example, but still need to convert this binary data into an image, here's a useful code snippet:
BitmapImage sourceImage = new BitmapImage();
using (MemoryStream ms = new MemoryStream(imageBinary))
{
sourceImage.BeginInit();
sourceImage.CacheOption = BitmapCacheOption.OnLoad;
sourceImage.StreamSource = ms;
sourceImage.EndInit();
}