130 lines
4.9 KiB
C#
Raw Normal View History

Adds BgcodeThumbnailProvider and BgcodePreviewHandler (#38667) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [X] **Closes:** #30352 - [X] **Communication:** I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected - [X] **Tests:** Added/updated and all pass - [X] **Localization:** All end user facing strings can be localized - [ ] **Dev docs:** Added/updated - [X] **New binaries:** Added on the required places - [X] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [X] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments ![image](https://github.com/user-attachments/assets/62c0cbbb-fbca-4bb3-82fe-696ba40da83d) ![image](https://github.com/user-attachments/assets/3f2f1346-91fb-4f49-85b9-8cd6e19e68e9) <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed - Close PowerToys if installed in your machine - Full build the solution: PowerToys.sln - Start PowerToys from PowerToys\x64\Debug\PowerToys.exe or PowerToys\x64\Release\PowerToys.exe - Toggle the "Binary G-code thumbnail previewer" setting to enable - Open HelperFiles folder on the tests and check if the icon changes to an image - Check explorer preview to see if image is also shown there --------- Co-authored-by: leileizhang <leilzh@microsoft.com>
2025-07-10 10:20:30 +01:00
// 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.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Bgcode file helper class.
/// </summary>
public static class BgcodeHelper
{
private const uint MagicNumber = 'G' | 'C' << 8 | 'D' << 16 | 'E' << 24;
/// <summary>
/// Gets any thumbnails found in a bgcode file.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/> instance to the bgcode file.</param>
/// <returns>The thumbnails found in a bgcode file.</returns>
public static IEnumerable<BgcodeThumbnail> GetThumbnails(BinaryReader reader)
{
var magicNumber = reader.ReadUInt32();
if (magicNumber != MagicNumber)
{
throw new InvalidDataException("Invalid magic number.");
}
var version = reader.ReadUInt32();
if (version != 1)
{
// Version 1 is the only one that exists
throw new InvalidDataException("Unsupported version.");
}
var checksum = (BgcodeChecksumType)reader.ReadUInt16();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
var blockType = (BgcodeBlockType)reader.ReadUInt16();
var compression = (BgcodeCompressionType)reader.ReadUInt16();
var uncompressedSize = reader.ReadUInt32();
var size = compression == BgcodeCompressionType.NoCompression ? uncompressedSize : reader.ReadUInt32();
switch (blockType)
{
case BgcodeBlockType.FileMetadataBlock:
case BgcodeBlockType.PrinterMetadataBlock:
case BgcodeBlockType.PrintMetadataBlock:
case BgcodeBlockType.SlicerMetadataBlock:
case BgcodeBlockType.GCodeBlock:
reader.BaseStream.Seek(2 + size, SeekOrigin.Current); // Skip
break;
case BgcodeBlockType.ThumbnailBlock:
var format = (BgcodeThumbnailFormat)reader.ReadUInt16();
reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip width and height
var data = ReadAndDecompressData(reader, compression, (int)size);
if (data != null)
{
yield return new BgcodeThumbnail(format, data);
}
break;
}
if (checksum == BgcodeChecksumType.CRC32)
{
reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip checksum
}
}
}
/// <summary>
/// Gets the best thumbnail available in a bgcode file.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/> instance to the gcode file.</param>
/// <returns>The best thumbnail available in the gcode file.</returns>
public static BgcodeThumbnail? GetBestThumbnail(BinaryReader reader)
{
return GetThumbnails(reader)
.OrderByDescending(x => x.Format switch
{
BgcodeThumbnailFormat.PNG => 2,
BgcodeThumbnailFormat.QOI => 1,
BgcodeThumbnailFormat.JPG => 0,
_ => 0,
})
.ThenByDescending(x => x.Data.Length)
.FirstOrDefault();
}
private static byte[]? ReadAndDecompressData(BinaryReader reader, BgcodeCompressionType compression, int size)
{
// Though the spec doesn't actually mention it, the reference encoder code never applies compression to thumbnails data
// which makes complete sense as this data is PNG, JPEG or QOI encoded so already compressed as much as possible!
switch (compression)
{
case BgcodeCompressionType.NoCompression:
return reader.ReadBytes(size);
case BgcodeCompressionType.DeflateAlgorithm:
var buffer = new byte[size];
using (var deflateStream = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true))
{
deflateStream.ReadExactly(buffer, 0, size);
}
return buffer;
default:
reader.BaseStream.Seek(size, SeekOrigin.Current); // Skip unknown or unsupported compression types
return null;
}
}
}
}