| File: System\Windows\Forms\Controls\ImageList\ImageListStreamer.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO.Compression; using System.Private.Windows.BinaryFormat; using System.Runtime.Serialization; using Windows.Win32.System.Com; namespace System.Windows.Forms; [Serializable] // This type is participating in resx serialization scenarios. public sealed class ImageListStreamer : ISerializable, IDisposable { // Compressed magic header. If we see this, the image stream is compressed. private static ReadOnlySpan<byte> HeaderMagic => "MSFt"u8; private static readonly Lock s_syncObject = new(); private readonly ImageList? _imageList; private ImageList.NativeImageList? _nativeImageList; internal ImageListStreamer(ImageList imageList) => _imageList = imageList; // Used by binary serialization private ImageListStreamer(SerializationInfo info, StreamingContext context) { if (info.GetValue<byte[]>("Data") is { } data) { Deserialize(data); } } internal ImageListStreamer(byte[] data) => Deserialize(data); /// <summary> /// Compresses the given input, returning a new array that represents the compressed data. /// </summary> private static byte[] Compress(ReadOnlySpan<byte> input) { int length = RunLengthEncoder.GetEncodedLength(input) + HeaderMagic.Length; byte[] output = new byte[length]; SpanWriter<byte> writer = new(output); writer.TryWrite(HeaderMagic); RunLengthEncoder.TryEncode(input, writer.Span[writer.Position..], out int written); Debug.Assert(written == length - HeaderMagic.Length, "RLE compression failure"); return output; } /// <summary> /// Decompresses the given input, returning a new array that represents the uncompressed data. /// </summary> private static byte[] Decompress(byte[] input) { SpanReader<byte> reader = new(input); if (!reader.TryAdvancePast(HeaderMagic)) { // Not compressed, return the original return input; } ReadOnlySpan<byte> remaining = reader.Span[reader.Position..]; int length = RunLengthEncoder.GetDecodedLength(remaining); byte[] output = new byte[length]; RunLengthEncoder.TryDecode(remaining, output, out int written); Debug.Assert(written == length, "RLE decompression failure"); return output; } private void Deserialize(byte[] data) { // We enclose this ImageList handle create in a theming scope. using ThemingScope scope = new(Application.UseVisualStyles); using MemoryStream memoryStream = new(Decompress(data)); lock (s_syncObject) { PInvoke.InitCommonControls(); _nativeImageList = new ImageList.NativeImageList(new ComManagedStream(memoryStream)); } if (_nativeImageList.HIMAGELIST.IsNull) { throw new InvalidOperationException(SR.ImageListStreamerLoadFailed); } } public void GetObjectData(SerializationInfo si, StreamingContext context) => si.AddValue("Data", Serialize()); internal byte[] Serialize() { using MemoryStream stream = new(); if (!WriteImageList(stream)) { throw new InvalidOperationException(SR.ImageListStreamerSaveFailed); } ReadOnlySpan<byte> buffer = stream.GetBuffer().AsSpan()[..(int)stream.Length]; return Compress(buffer); } internal void GetObjectData(Stream stream) { if (!WriteImageList(stream)) { throw new InvalidOperationException(SR.ImageListStreamerSaveFailed); } } internal ImageList.NativeImageList? GetNativeImageList() => _nativeImageList; private bool WriteImageList(Stream stream) { HandleRef<HIMAGELIST> handle = default; if (_imageList is not null) { handle = new(_imageList, (HIMAGELIST)_imageList.Handle); } else if (_nativeImageList is not null) { handle = new(_nativeImageList, _nativeImageList.HIMAGELIST); } if (handle.IsNull) { return false; } try { return PInvoke.ImageList.WriteEx( handle, IMAGE_LIST_WRITE_STREAM_FLAGS.ILP_DOWNLEVEL, stream).Succeeded; } catch (EntryPointNotFoundException) { // Not running on ComCtl32 v6, fall back to the old API. } return PInvoke.ImageList.Write(handle, stream); } public void Dispose() { _nativeImageList?.Dispose(); _nativeImageList = null; } }