Pedro Lamas 071f5d7bcc
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 17:20:30 +08:00

130 lines
4.9 KiB
C#

// 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;
}
}
}
}