/
ImageConverter.cs
158 lines (139 loc) · 5.59 KB
/
ImageConverter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers.Binary;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
namespace System.Drawing;
public 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)
{
Debug.Assert(value != null, "value is null.");
// 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 object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (destinationType == typeof(string))
{
if (value == null)
{
return SR.none;
}
else if (value is Image)
{
return value.ToString()!;
}
}
else if (destinationType == typeof(byte[]))
{
if (value == null)
{
return Array.Empty<byte>();
}
else if (value is Image image)
{
using var ms = new MemoryStream();
ImageFormat dest = image.RawFormat;
// Jpeg loses data, so we don't want to use it to serialize.
if (dest == ImageFormat.Jpeg)
{
dest = ImageFormat.Png;
}
// If we don't find an Encoder (for things like Icon), we
// just switch back to PNG.
ImageCodecInfo codec = FindEncoder(dest) ?? FindEncoder(ImageFormat.Png)!;
image.Save(ms, codec, null);
return ms.ToArray();
}
}
throw GetConvertFromException(value);
}
// Find any random encoder which supports this format.
private static ImageCodecInfo? FindEncoder(ImageFormat imageformat)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID.Equals(imageformat.Guid))
return codec;
}
return null;
}
[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)
{
try
{
short signature = BinaryPrimitives.ReadInt16LittleEndian(rawData);
if (signature != 0x1c15)
{
return null;
}
// The data is in the form of OBJECTHEADER. It's an encoded format that Access uses to push images into the DB.
//
// The layout of OBJECTHEADER is as follows - we only need the signature
// and headersize fields, which need to be read as little-endian data:
//
// [StructLayout(LayoutKind.Sequential)]
// private struct OBJECTHEADER
// {
// public short signature; // it's always 0x1c15
// public short headersize;
// public short objectType;
// public short nameLen;
// public short classLen;
// public short nameOffset;
// public short classOffset;
// public short width;
// public short height;
// public IntPtr pInfo;
// }
short headersize = BinaryPrimitives.ReadInt16LittleEndian(rawData.Slice(2, 2));
// pHeader.signature will always be 0x1c15.
// "PBrush" should be the 6 chars after position 12 as well.
if (rawData.Length <= headersize + 18 ||
!rawData.Slice(headersize + 12, 6).SequenceEqual("PBrush"u8))
{
return null;
}
// We can safely trust that we've got a bitmap.
// The start of our bitmap data in the rawdata is always 78.
return new MemoryStream(rawData.Slice(78).ToArray());
}
catch (OutOfMemoryException) // This exception may be caused by creating a new MemoryStream.
{
}
catch (ArgumentOutOfRangeException) // This exception may get thrown by MemoryMarshal when input array size is less than the size of the output type.
{
}
return null;
}
}