mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 14:59:16 +08:00
[FileExplorerAddOns]Improves handling of Gcode Thumbnails (#27947)
* Improves handling of Gcode Thumbnails * Remove Peek support * Moves GcodeHelper to PreviewHandlerCommon * Reverts minor change * Skip unknown data on GcodeHelper.GetBestThumbnail * Replaces QOI.Core with QoiImage * Fixes spellchecker * Reverts changes to NOTICE.md * Minor QoiImage improvements * Use custom QoiPixel struct * Add MIT notice for the QOI reference code * Fix spellcheck for the MIT notice * Update NOTICE.md tweaked notice a bit
This commit is contained in:
parent
cc454701b8
commit
4ba1d83cf5
2
.github/actions/spell-check/allow/names.txt
vendored
2
.github/actions/spell-check/allow/names.txt
vendored
@ -35,6 +35,7 @@ mshtang
|
||||
Myrvold
|
||||
naveensrinivasan
|
||||
nVidia
|
||||
phoboslab
|
||||
Ponten
|
||||
Pooja
|
||||
robmen
|
||||
@ -44,6 +45,7 @@ skycommand
|
||||
snickler
|
||||
sinclairinat
|
||||
streamjsonrpc
|
||||
Szablewski
|
||||
tilovell
|
||||
TheJoeFin
|
||||
Triet
|
||||
|
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@ -1014,6 +1014,7 @@ lstrlen
|
||||
LTRB
|
||||
LTRREADING
|
||||
luid
|
||||
LUMA
|
||||
lusrmgr
|
||||
LVal
|
||||
LWA
|
||||
@ -1496,6 +1497,7 @@ qianlifeng
|
||||
qit
|
||||
QITAB
|
||||
QITABENT
|
||||
qoi
|
||||
qps
|
||||
Quarternary
|
||||
QUERYENDSESSION
|
||||
@ -2250,6 +2252,7 @@ xsi
|
||||
XStr
|
||||
XUP
|
||||
XVIRTUALSCREEN
|
||||
xxxxxx
|
||||
YAxis
|
||||
ycv
|
||||
Yeet
|
||||
|
28
NOTICE.md
28
NOTICE.md
@ -67,6 +67,34 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
### The Quite OK Image Format reference decoder
|
||||
|
||||
**Source**: https://github.com/phoboslab/qoi
|
||||
|
||||
**Note**: [@pedrolamas](https://github.com/pedrolamas) translated and adapted the reference decoder code to C# that is in PowerToys from the original C++ implementation.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Dominic Szablewski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## Utility: ImageResizer
|
||||
|
||||
### Brice Lams's Image Resizer License
|
||||
|
@ -2,14 +2,6 @@
|
||||
// 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.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using Common;
|
||||
using Common.Utilities;
|
||||
using Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events;
|
||||
@ -67,7 +59,9 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
|
||||
|
||||
using (var reader = new StreamReader(fs))
|
||||
{
|
||||
thumbnail = GetThumbnail(reader);
|
||||
var gcodeThumbnail = GcodeHelper.GetBestThumbnail(reader);
|
||||
|
||||
thumbnail = gcodeThumbnail?.GetBitmap();
|
||||
}
|
||||
|
||||
_infoBarAdded = false;
|
||||
@ -92,66 +86,6 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the G-code content searching for thumbnails and returns the largest.
|
||||
/// </summary>
|
||||
/// <param name="reader">The TextReader instance for the G-code content.</param>
|
||||
/// <returns>A thumbnail extracted from the G-code content.</returns>
|
||||
public static Bitmap GetThumbnail(TextReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap thumbnail = null;
|
||||
|
||||
var bitmapBase64 = GetBase64Thumbnails(reader)
|
||||
.OrderByDescending(x => x.Length)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrEmpty(bitmapBase64))
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(bitmapBase64);
|
||||
|
||||
thumbnail = new Bitmap(new MemoryStream(bitmapBytes));
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all thumbnails in base64 format found on the G-code data.
|
||||
/// </summary>
|
||||
/// <param name="reader">The TextReader instance for the G-code content.</param>
|
||||
/// <returns>An enumeration of thumbnails in base64 format found on the G-code.</returns>
|
||||
private static IEnumerable<string> GetBase64Thumbnails(TextReader reader)
|
||||
{
|
||||
string line;
|
||||
StringBuilder capturedText = null;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("; thumbnail begin", StringComparison.InvariantCulture))
|
||||
{
|
||||
capturedText = new StringBuilder();
|
||||
}
|
||||
else if (line == "; thumbnail end")
|
||||
{
|
||||
if (capturedText != null)
|
||||
{
|
||||
yield return capturedText.ToString();
|
||||
|
||||
capturedText = null;
|
||||
}
|
||||
}
|
||||
else if (capturedText != null)
|
||||
{
|
||||
capturedText.Append(line[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when RichtextBox is resized.
|
||||
/// </summary>
|
||||
|
@ -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.Drawing.Drawing2D;
|
||||
using System.Text;
|
||||
using Common.Utilities;
|
||||
|
||||
namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
|
||||
{
|
||||
@ -45,64 +45,23 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap thumbnail = null;
|
||||
var gcodeThumbnail = GcodeHelper.GetBestThumbnail(reader);
|
||||
|
||||
var bitmapBase64 = GetBase64Thumbnails(reader)
|
||||
.OrderByDescending(x => x.Length)
|
||||
.FirstOrDefault();
|
||||
var thumbnail = gcodeThumbnail?.GetBitmap();
|
||||
|
||||
if (!string.IsNullOrEmpty(bitmapBase64))
|
||||
if (thumbnail != null && thumbnail.Width != cx && thumbnail.Height != cx)
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(bitmapBase64);
|
||||
|
||||
thumbnail = new Bitmap(new MemoryStream(bitmapBytes));
|
||||
|
||||
if (thumbnail.Width != cx && thumbnail.Height != cx)
|
||||
{
|
||||
// We are not the appropriate size for caller. Resize now while
|
||||
// respecting the aspect ratio.
|
||||
float scale = Math.Min((float)cx / thumbnail.Width, (float)cx / thumbnail.Height);
|
||||
int scaleWidth = (int)(thumbnail.Width * scale);
|
||||
int scaleHeight = (int)(thumbnail.Height * scale);
|
||||
thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight);
|
||||
}
|
||||
// We are not the appropriate size for caller. Resize now while
|
||||
// respecting the aspect ratio.
|
||||
float scale = Math.Min((float)cx / thumbnail.Width, (float)cx / thumbnail.Height);
|
||||
int scaleWidth = (int)(thumbnail.Width * scale);
|
||||
int scaleHeight = (int)(thumbnail.Height * scale);
|
||||
thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight);
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all thumbnails in base64 format found on the G-code data.
|
||||
/// </summary>
|
||||
/// <param name="reader">The TextReader instance for the G-code content.</param>
|
||||
/// <returns>An enumeration of thumbnails in base64 format found on the G-code.</returns>
|
||||
private static IEnumerable<string> GetBase64Thumbnails(TextReader reader)
|
||||
{
|
||||
string line;
|
||||
StringBuilder capturedText = null;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("; thumbnail begin", StringComparison.InvariantCulture))
|
||||
{
|
||||
capturedText = new StringBuilder();
|
||||
}
|
||||
else if (line == "; thumbnail end")
|
||||
{
|
||||
if (capturedText != null)
|
||||
{
|
||||
yield return capturedText.ToString();
|
||||
|
||||
capturedText = null;
|
||||
}
|
||||
}
|
||||
else if (capturedText != null)
|
||||
{
|
||||
capturedText.Append(line[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resize the image with high quality to the specified width and height.
|
||||
/// </summary>
|
||||
|
@ -19,14 +19,17 @@ namespace GcodePreviewHandlerUnitTests
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "new Exception() is fine in test projects.")]
|
||||
public class GcodePreviewHandlerTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void GcodePreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled()
|
||||
[DataTestMethod]
|
||||
[DataRow("HelperFiles/sample.gcode")]
|
||||
[DataRow("HelperFiles/sample_JPG.gcode")]
|
||||
[DataRow("HelperFiles/sample_QOI.gcode")]
|
||||
public void GcodePreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled(string filePath)
|
||||
{
|
||||
// Arrange
|
||||
using (var gcodePreviewHandlerControl = new GcodePreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
var file = File.ReadAllBytes("HelperFiles/sample.gcode");
|
||||
var file = File.ReadAllBytes(filePath);
|
||||
|
||||
gcodePreviewHandlerControl.DoPreview<IStream>(GetMockStream(file));
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="HelperFiles\sample.gcode" />
|
||||
<None Remove="HelperFiles\sample_JPG.gcode" />
|
||||
<None Remove="HelperFiles\sample_QOI.gcode" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -41,6 +43,12 @@
|
||||
<Content Include="HelperFiles\sample.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_JPG.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_QOI.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Compile Include="..\STATestClassAttribute.cs" Link="STATestClassAttribute.cs" />
|
||||
<Compile Include="..\STATestMethodAttribute.cs" Link="STATestMethodAttribute.cs" />
|
||||
</ItemGroup>
|
||||
|
@ -2,28 +2,24 @@
|
||||
// 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 System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Common.ComInterlop;
|
||||
using Microsoft.PowerToys.STATestExtension;
|
||||
using Microsoft.PowerToys.ThumbnailHandler.Gcode;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace GcodeThumbnailProviderUnitTests
|
||||
{
|
||||
[STATestClass]
|
||||
public class GcodeThumbnailProviderTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void GetThumbnailValidStreamGcode()
|
||||
[DataTestMethod]
|
||||
[DataRow("HelperFiles/sample.gcode")]
|
||||
[DataRow("HelperFiles/sample_JPG.gcode")]
|
||||
[DataRow("HelperFiles/sample_QOI.gcode")]
|
||||
public void GetThumbnailValidStreamGcode(string filePath)
|
||||
{
|
||||
// Act
|
||||
var filePath = "HelperFiles/sample.gcode";
|
||||
|
||||
GcodeThumbnailProvider provider = new GcodeThumbnailProvider(filePath);
|
||||
|
||||
Bitmap bitmap = provider.GetThumbnail(256);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="HelperFiles\sample.gcode" />
|
||||
<None Remove="HelperFiles\sample_JPG.gcode" />
|
||||
<None Remove="HelperFiles\sample_QOI.gcode" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -44,5 +46,11 @@
|
||||
<Content Include="HelperFiles\sample.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_JPG.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_QOI.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<AssemblyTitle>PowerToys.PreviewHandlerCommon</AssemblyTitle>
|
||||
@ -9,6 +9,8 @@
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
81
src/modules/previewpane/common/Utilities/GcodeHelper.cs
Normal file
81
src/modules/previewpane/common/Utilities/GcodeHelper.cs
Normal file
@ -0,0 +1,81 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
72
src/modules/previewpane/common/Utilities/GcodeThumbnail.cs
Normal file
72
src/modules/previewpane/common/Utilities/GcodeThumbnail.cs
Normal file
@ -0,0 +1,72 @@
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// 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,
|
||||
}
|
||||
}
|
177
src/modules/previewpane/common/Utilities/QoiImage.cs
Normal file
177
src/modules/previewpane/common/Utilities/QoiImage.cs
Normal file
@ -0,0 +1,177 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
@ -17,7 +18,7 @@ namespace Common.Utilities
|
||||
/// </remarks>
|
||||
public class ReadonlyStream : Stream
|
||||
{
|
||||
private IStream _stream;
|
||||
private IStream? _stream;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadonlyStream"/> class.
|
||||
@ -238,6 +239,7 @@ namespace Common.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_stream))]
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (_stream == null)
|
||||
|
@ -20,12 +20,12 @@ namespace Common
|
||||
/// <summary>
|
||||
/// WebView2 Control to display Svg.
|
||||
/// </summary>
|
||||
private WebView2 _browser;
|
||||
private WebView2? _browser;
|
||||
|
||||
/// <summary>
|
||||
/// WebView2 Environment
|
||||
/// </summary>
|
||||
private CoreWebView2Environment _webView2Environment;
|
||||
private CoreWebView2Environment? _webView2Environment;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the virtual host
|
||||
@ -38,7 +38,7 @@ namespace Common
|
||||
/// <remarks>
|
||||
/// Source: https://stackoverflow.com/a/283917/14774889
|
||||
/// </remarks>
|
||||
public static string AssemblyDirectory
|
||||
public static string? AssemblyDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -60,7 +60,7 @@ namespace Common
|
||||
_browser = new WebView2();
|
||||
_browser.Dock = DockStyle.Fill;
|
||||
_browser.Visible = true;
|
||||
_browser.NavigationCompleted += (object sender, CoreWebView2NavigationCompletedEventArgs args) =>
|
||||
_browser.NavigationCompleted += (object? sender, CoreWebView2NavigationCompletedEventArgs args) =>
|
||||
{
|
||||
// Put here logic needed after WebView2 control is done navigating to url/page
|
||||
};
|
||||
@ -83,7 +83,7 @@ namespace Common
|
||||
_browser.NavigateToString("Test");
|
||||
|
||||
// Or navigate to Uri
|
||||
_browser.Source = new Uri(filePath);
|
||||
_browser.Source = new Uri(filePath!);
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user