| File: System\Drawing\ImageConverter.cs | Web Access |
| Project: src\winforms\src\System.Drawing.Common\src\System.Drawing.Common.csproj (System.Drawing.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Globalization; using System.IO; namespace System.Drawing; public partial class ImageConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type? sourceType) { return sourceType == typeof(byte[]) || sourceType == typeof(Icon); } public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) { return destinationType == typeof(byte[]) || destinationType == typeof(string); } public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { if (value is Icon icon) { return icon.ToBitmap(); } if (value is byte[] bytes) { // Try to get memory stream for images with ole header. MemoryStream memStream = GetBitmapStream(bytes) ?? new MemoryStream(bytes); return Image.FromStream(memStream); } else { return base.ConvertFrom(context, culture, value); } } public override unsafe object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { if (destinationType == typeof(string)) { if (value is null) { return SR.none; } else if (value is Image) { return value.ToString()!; } } else if (destinationType == typeof(byte[])) { if (value is null) { return Array.Empty<byte>(); } else if (value is Image image) { using MemoryStream ms = new(); image.Save(ms); return ms.ToArray(); } } throw GetConvertFromException(value); } [RequiresUnreferencedCode("The Type of value cannot be statically discovered. The public parameterless constructor or the 'Default' static field may be trimmed from the Attribute's Type.")] public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object? value, Attribute[]? attributes) { return TypeDescriptor.GetProperties(typeof(Image), attributes); } public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true; private static unsafe MemoryStream? GetBitmapStream(ReadOnlySpan<byte> rawData) { // Why we try to get out the bitmap stream from the Access (Jet) storage format isn't 100% clear. It might // have something to do with Visual Basic 3.0's support for Jet, but that has not been confirmed. The same // OLEOBJHEADER code is in VB6 (but not WFC). Now that the data is clearly described here, fast, and // thoroughly checked, it's probably best to just leave it in place. // Try to verify the incoming data to the level that Windows / Office would. SpanReader<byte> reader = new(rawData); if (!reader.TryRead(out OLEOBJHEADER header)) { return null; } // Validate the OLEOBJHEADER if (header.typ != OLEOBJHEADER.OLEOBJID || header.oot != OleObjectType.OT_EMBEDDED || header.ibName != sizeof(OLEOBJHEADER) || header.ibClass != sizeof(OLEOBJHEADER) + header.cchName || header.cbHdr != sizeof(OLEOBJHEADER) + header.cchClass + header.cchName || header.ptSize != new Point(-1, -1) || !reader.TryRead(header.cchName, out ReadOnlySpan<byte> nameSpan) || !reader.TryRead(header.cchClass, out ReadOnlySpan<byte> classSpan)) { return null; } // Unknown if this would ever be anything else in practice. Debug.Assert(nameSpan.SequenceEqual("Bitmap Image\0"u8)); Debug.Assert(classSpan.SequenceEqual("Paint.Picture\0"u8)); // At this point we're at the start of the OLE 1.0 data. // [MS-OLEDS]: Object Linking and Embedding (OLE) // 2.2.5 EmbeddedObject // https://learn.microsoft.com/openspecs/windows_protocols/ms-oleds/3395d95d-97f0-49ff-b792-28d331f254f1 // Read and validate the ObjectHeader (version is the first uint) if (!reader.TryRead(out uint _) || !reader.TryRead(out FMTID format) || format != FMTID.FMTID_EMBED || !reader.TryRead(out int classLength) || !reader.TryRead(classLength, out ReadOnlySpan<byte> className) // MSPaint, which was originally PC Paintbrush || !className.SequenceEqual("PBrush\0"u8) || !reader.TryRead(out int topicLength) || topicLength != 0 || !reader.TryRead(out int itemNameLength) || itemNameLength != 0) { return null; } // Read the EmbeddedObjectData if (!reader.TryRead(out int dataLength) || !reader.TryRead(dataLength, out var data)) { return null; } // We could avoid a copy here with some effort, but this whole code path seems to be extremely rare. return new MemoryStream(data.ToArray()); } }