mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
[Peek]Adds QOI file support (#29919)
* Adds QOI support to Peek * Reduce allocations on QoiImage * Add to QOI to Peek's NOTICE as well. * Ensure file stream is closed after reading QOI
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -77,6 +77,31 @@ namespace Peek.Common.Extensions
|
||||
return size;
|
||||
}
|
||||
|
||||
public static Size? GetQoiSize(this IFileSystemItem item)
|
||||
{
|
||||
Size? size = null;
|
||||
using (FileStream stream = new FileStream(item.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
|
||||
{
|
||||
if (stream.Length >= 12)
|
||||
{
|
||||
stream.Position = 4;
|
||||
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
uint widthValue = BinaryPrimitives.ReadUInt32BigEndian(reader.ReadBytes(4));
|
||||
uint heightValue = BinaryPrimitives.ReadUInt32BigEndian(reader.ReadBytes(4));
|
||||
|
||||
if (widthValue > 0 && heightValue > 0)
|
||||
{
|
||||
size = new Size(widthValue, heightValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public static ulong GetSizeInBytes(this IFileSystemItem item)
|
||||
{
|
||||
ulong sizeInBytes = 0;
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Peek.FilePreviewer.Previewers.Helpers
|
||||
public static async Task<BitmapSource> GetBitmapFromHBitmapAsync(IntPtr hbitmap, bool isSupportingTransparency, CancellationToken cancellationToken)
|
||||
{
|
||||
Bitmap? bitmap = null;
|
||||
Bitmap? tempBitmapForDeletion = null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -26,15 +25,29 @@ namespace Peek.FilePreviewer.Previewers.Helpers
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
return await BitmapToImageSource(bitmap, isSupportingTransparency, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap?.Dispose();
|
||||
|
||||
// delete HBitmap to avoid memory leaks
|
||||
NativeMethods.DeleteObject(hbitmap);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<BitmapSource> BitmapToImageSource(Bitmap bitmap, bool isSupportingTransparency, CancellationToken cancellationToken)
|
||||
{
|
||||
Bitmap? transparentBitmap = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (isSupportingTransparency && bitmap.PixelFormat == PixelFormat.Format32bppRgb)
|
||||
{
|
||||
var bitmapRectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
|
||||
var bitmapData = bitmap.LockBits(bitmapRectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat);
|
||||
|
||||
var transparentBitmap = new Bitmap(bitmapData.Width, bitmapData.Height, bitmapData.Stride, PixelFormat.Format32bppArgb, bitmapData.Scan0);
|
||||
|
||||
// Can't dispose of original bitmap yet as that causes crashes on png files. Saving it for later disposal after saving to stream.
|
||||
tempBitmapForDeletion = bitmap;
|
||||
transparentBitmap = new Bitmap(bitmapData.Width, bitmapData.Height, bitmapData.Stride, PixelFormat.Format32bppArgb, bitmapData.Scan0);
|
||||
bitmap = transparentBitmap;
|
||||
}
|
||||
|
||||
@@ -54,11 +67,7 @@ namespace Peek.FilePreviewer.Previewers.Helpers
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap?.Dispose();
|
||||
tempBitmapForDeletion?.Dispose();
|
||||
|
||||
// delete HBitmap to avoid memory leaks
|
||||
NativeMethods.DeleteObject(hbitmap);
|
||||
transparentBitmap?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.FilePreviewCommon;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
@@ -91,6 +92,14 @@ namespace Peek.FilePreviewer.Previewers
|
||||
ImageSize = size.Value;
|
||||
}
|
||||
}
|
||||
else if (IsQoi(Item))
|
||||
{
|
||||
var size = await Task.Run(Item.GetQoiSize);
|
||||
if (size != null)
|
||||
{
|
||||
ImageSize = size.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImageSize = await Task.Run(Item.GetImageSize);
|
||||
@@ -257,6 +266,12 @@ namespace Peek.FilePreviewer.Previewers
|
||||
|
||||
Preview = source;
|
||||
}
|
||||
else if (IsQoi(Item))
|
||||
{
|
||||
using var bitmap = QoiImage.FromStream(stream);
|
||||
|
||||
Preview = await BitmapHelper.BitmapToImageSource(bitmap, true, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bitmap = new BitmapImage();
|
||||
@@ -286,6 +301,11 @@ namespace Peek.FilePreviewer.Previewers
|
||||
return item.Extension == ".svg";
|
||||
}
|
||||
|
||||
private bool IsQoi(IFileSystemItem item)
|
||||
{
|
||||
return item.Extension == ".qoi";
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
lowQualityThumbnailPreview = null;
|
||||
@@ -367,6 +387,8 @@ namespace Peek.FilePreviewer.Previewers
|
||||
".cr3",
|
||||
|
||||
".svg",
|
||||
|
||||
".qoi",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Common;
|
||||
using Common.Utilities;
|
||||
using Microsoft.PowerToys.FilePreviewCommon;
|
||||
using Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using Common.Utilities;
|
||||
using Microsoft.PowerToys.FilePreviewCommon;
|
||||
|
||||
namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
|
||||
{
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\Common\PreviewHandlerCommon.csproj" />
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Common;
|
||||
using Microsoft.PowerToys.FilePreviewCommon;
|
||||
using Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PreviewHandlerCommon.Utilities;
|
||||
|
||||
namespace Microsoft.PowerToys.PreviewHandler.Qoi
|
||||
{
|
||||
@@ -63,7 +63,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Qoi
|
||||
throw new ArgumentException($"{nameof(dataSource)} for {nameof(QoiPreviewHandlerControl)} must be a string but was a '{typeof(T)}'");
|
||||
}
|
||||
|
||||
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
thumbnail = QoiImage.FromStream(fs);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using PreviewHandlerCommon.Utilities;
|
||||
using Microsoft.PowerToys.FilePreviewCommon;
|
||||
|
||||
namespace Microsoft.PowerToys.ThumbnailHandler.Qoi
|
||||
{
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\Common\PreviewHandlerCommon.csproj" />
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gcode file helper class.
|
||||
/// </summary>
|
||||
public static class GcodeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets any thumbnails found in a gcode file.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="TextReader"/> instance to the gcode file.</param>
|
||||
/// <returns>The thumbnails found in a gcode file.</returns>
|
||||
public static IEnumerable<GcodeThumbnail> GetThumbnails(TextReader reader)
|
||||
{
|
||||
string? line;
|
||||
var format = GcodeThumbnailFormat.Unknown;
|
||||
StringBuilder? capturedText = null;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("; thumbnail", StringComparison.InvariantCulture))
|
||||
{
|
||||
var parts = line[11..].Split(" ");
|
||||
|
||||
switch (parts[1])
|
||||
{
|
||||
case "begin":
|
||||
format = parts[0].ToUpperInvariant() switch
|
||||
{
|
||||
"" => GcodeThumbnailFormat.PNG,
|
||||
"_JPG" => GcodeThumbnailFormat.JPG,
|
||||
"_QOI" => GcodeThumbnailFormat.QOI,
|
||||
_ => GcodeThumbnailFormat.Unknown,
|
||||
};
|
||||
capturedText = new StringBuilder();
|
||||
|
||||
break;
|
||||
|
||||
case "end":
|
||||
if (capturedText != null)
|
||||
{
|
||||
yield return new GcodeThumbnail(format, capturedText.ToString());
|
||||
|
||||
capturedText = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
capturedText?.Append(line[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the best thumbnail available in a gcode file.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="TextReader"/> instance to the gcode file.</param>
|
||||
/// <returns>The best thumbnail available in the gcode file.</returns>
|
||||
public static GcodeThumbnail? GetBestThumbnail(TextReader reader)
|
||||
{
|
||||
return GetThumbnails(reader)
|
||||
.Where(x => x.Format != GcodeThumbnailFormat.Unknown)
|
||||
.OrderByDescending(x => (int)x.Format)
|
||||
.ThenByDescending(x => x.Data.Length)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using PreviewHandlerCommon.Utilities;
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a gcode thumbnail.
|
||||
/// </summary>
|
||||
public class GcodeThumbnail
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the gcode thumbnail image format.
|
||||
/// </summary>
|
||||
public GcodeThumbnailFormat Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gcode thumbnail image data in base64.
|
||||
/// </summary>
|
||||
public string Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GcodeThumbnail"/> class.
|
||||
/// </summary>
|
||||
/// <param name="format">The gcode thumbnail image format.</param>
|
||||
/// <param name="data">The gcode thumbnail image data in base64.</param>
|
||||
public GcodeThumbnail(GcodeThumbnailFormat format, string data)
|
||||
{
|
||||
Format = format;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Bitmap"/> representing this thumbnail.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Bitmap"/> representing this thumbnail.</returns>
|
||||
public Bitmap? GetBitmap()
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
case GcodeThumbnailFormat.JPG:
|
||||
case GcodeThumbnailFormat.PNG:
|
||||
return BitmapFromBase64String();
|
||||
|
||||
case GcodeThumbnailFormat.QOI:
|
||||
return BitmapFromQoiBase64String();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap BitmapFromBase64String()
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(Data);
|
||||
|
||||
return new Bitmap(new MemoryStream(bitmapBytes));
|
||||
}
|
||||
|
||||
private Bitmap BitmapFromQoiBase64String()
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(Data);
|
||||
|
||||
return QoiImage.FromStream(new MemoryStream(bitmapBytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// The gcode thumbnail image format.
|
||||
/// </summary>
|
||||
public enum GcodeThumbnailFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown image format.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// JPG image format.
|
||||
/// </summary>
|
||||
JPG,
|
||||
|
||||
/// <summary>
|
||||
/// QOI image format.
|
||||
/// </summary>
|
||||
QOI,
|
||||
|
||||
/// <summary>
|
||||
/// PNG image format.
|
||||
/// </summary>
|
||||
PNG,
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
//// Based on https://github.com/phoboslab/qoi/blob/master/qoi.h
|
||||
|
||||
namespace PreviewHandlerCommon.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// QOI Image helper.
|
||||
/// </summary>
|
||||
public static class QoiImage
|
||||
{
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
private const byte QOI_OP_INDEX = 0x00; // 00xxxxxx
|
||||
private const byte QOI_OP_DIFF = 0x40; // 01xxxxxx
|
||||
private const byte QOI_OP_LUMA = 0x80; // 10xxxxxx
|
||||
private const byte QOI_OP_RUN = 0xc0; // 11xxxxxx
|
||||
private const byte QOI_OP_RGB = 0xfe; // 11111110
|
||||
private const byte QOI_OP_RGBA = 0xff; // 11111111
|
||||
|
||||
private const byte QOI_MASK_2 = 0xc0; // 11000000
|
||||
|
||||
private const int QOI_MAGIC = 'q' << 24 | 'o' << 16 | 'i' << 8 | 'f';
|
||||
private const int QOI_HEADER_SIZE = 14;
|
||||
|
||||
private const uint QOI_PIXELS_MAX = 400000000;
|
||||
|
||||
private const byte QOI_PADDING_LENGTH = 8;
|
||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||
|
||||
private record struct QoiPixel(byte R, byte G, byte B, byte A)
|
||||
{
|
||||
public readonly int GetColorHash() => (R * 3) + (G * 5) + (B * 7) + (A * 11);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Bitmap"/> from the specified QOI data stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A <see cref="Stream"/> that contains the QOI data.</param>
|
||||
/// <returns>The <see cref="Bitmap"/> this method creates.</returns>
|
||||
/// <exception cref="ArgumentException">The stream does not have a valid QOI image format.</exception>
|
||||
public static Bitmap FromStream(Stream stream)
|
||||
{
|
||||
var fileSize = stream.Length;
|
||||
|
||||
if (fileSize < QOI_HEADER_SIZE + QOI_PADDING_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Not enough data for a QOI file");
|
||||
}
|
||||
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
var headerMagic = ReadUInt32BigEndian(reader);
|
||||
|
||||
if (headerMagic != QOI_MAGIC)
|
||||
{
|
||||
throw new ArgumentException("Invalid QOI file header");
|
||||
}
|
||||
|
||||
var width = ReadUInt32BigEndian(reader);
|
||||
var height = ReadUInt32BigEndian(reader);
|
||||
var channels = reader.ReadByte();
|
||||
var colorSpace = reader.ReadByte();
|
||||
|
||||
if (width == 0 || height == 0 || channels < 3 || channels > 4 || colorSpace > 1 || height >= QOI_PIXELS_MAX / width)
|
||||
{
|
||||
throw new ArgumentException("Invalid QOI file data");
|
||||
}
|
||||
|
||||
var pixelsCount = width * height;
|
||||
var pixels = new QoiPixel[pixelsCount];
|
||||
var index = new QoiPixel[64];
|
||||
|
||||
var pixel = new QoiPixel(0, 0, 0, 255);
|
||||
|
||||
var run = 0;
|
||||
var chunksLen = fileSize - QOI_PADDING_LENGTH;
|
||||
|
||||
for (var pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++)
|
||||
{
|
||||
if (run > 0)
|
||||
{
|
||||
run--;
|
||||
}
|
||||
else if (stream.Position < chunksLen)
|
||||
{
|
||||
var b1 = reader.ReadByte();
|
||||
|
||||
if (b1 == QOI_OP_RGB)
|
||||
{
|
||||
pixel.R = reader.ReadByte();
|
||||
pixel.G = reader.ReadByte();
|
||||
pixel.B = reader.ReadByte();
|
||||
}
|
||||
else if (b1 == QOI_OP_RGBA)
|
||||
{
|
||||
pixel.R = reader.ReadByte();
|
||||
pixel.G = reader.ReadByte();
|
||||
pixel.B = reader.ReadByte();
|
||||
pixel.A = reader.ReadByte();
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX)
|
||||
{
|
||||
pixel = index[b1];
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF)
|
||||
{
|
||||
pixel.R += (byte)(((b1 >> 4) & 0x03) - 2);
|
||||
pixel.G += (byte)(((b1 >> 2) & 0x03) - 2);
|
||||
pixel.B += (byte)((b1 & 0x03) - 2);
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA)
|
||||
{
|
||||
var b2 = reader.ReadByte();
|
||||
var vg = (b1 & 0x3f) - 32;
|
||||
pixel.R += (byte)(vg - 8 + ((b2 >> 4) & 0x0f));
|
||||
pixel.G += (byte)vg;
|
||||
pixel.B += (byte)(vg - 8 + (b2 & 0x0f));
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN)
|
||||
{
|
||||
run = b1 & 0x3f;
|
||||
}
|
||||
|
||||
index[pixel.GetColorHash() % 64] = pixel;
|
||||
}
|
||||
|
||||
pixels[pixelIndex] = pixel;
|
||||
}
|
||||
|
||||
return ConvertToBitmap(width, height, channels, pixels);
|
||||
}
|
||||
|
||||
private static Bitmap ConvertToBitmap(uint width, uint height, byte channels, QoiPixel[] pixels)
|
||||
{
|
||||
var pixelFormat = channels == 4 ? PixelFormat.Format32bppArgb : PixelFormat.Format24bppRgb;
|
||||
var bitmap = new Bitmap((int)width, (int)height, pixelFormat);
|
||||
|
||||
var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, pixelFormat);
|
||||
|
||||
unsafe
|
||||
{
|
||||
for (var pixelIndex = 0; pixelIndex < pixels.Length; pixelIndex++)
|
||||
{
|
||||
var pixel = pixels[pixelIndex];
|
||||
var bitmapPixel = (byte*)bitmapData.Scan0 + (pixelIndex * channels);
|
||||
|
||||
bitmapPixel[0] = pixel.B;
|
||||
bitmapPixel[1] = pixel.G;
|
||||
bitmapPixel[2] = pixel.R;
|
||||
if (channels == 4)
|
||||
{
|
||||
bitmapPixel[3] = pixel.A;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bitmapData);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static uint ReadUInt32BigEndian(BinaryReader reader)
|
||||
{
|
||||
var buffer = reader.ReadBytes(4);
|
||||
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user