/ src / common / FilePreviewCommon / BgcodeHelper.cs
BgcodeHelper.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System.Collections.Generic;
  6  using System.IO;
  7  using System.IO.Compression;
  8  using System.Linq;
  9  
 10  namespace Microsoft.PowerToys.FilePreviewCommon
 11  {
 12      /// <summary>
 13      /// Bgcode file helper class.
 14      /// </summary>
 15      public static class BgcodeHelper
 16      {
 17          private const uint MagicNumber = 'G' | 'C' << 8 | 'D' << 16 | 'E' << 24;
 18  
 19          /// <summary>
 20          /// Gets any thumbnails found in a bgcode file.
 21          /// </summary>
 22          /// <param name="reader">The <see cref="BinaryReader"/> instance to the bgcode file.</param>
 23          /// <returns>The thumbnails found in a bgcode file.</returns>
 24          public static IEnumerable<BgcodeThumbnail> GetThumbnails(BinaryReader reader)
 25          {
 26              var magicNumber = reader.ReadUInt32();
 27  
 28              if (magicNumber != MagicNumber)
 29              {
 30                  throw new InvalidDataException("Invalid magic number.");
 31              }
 32  
 33              var version = reader.ReadUInt32();
 34  
 35              if (version != 1)
 36              {
 37                  // Version 1 is the only one that exists
 38                  throw new InvalidDataException("Unsupported version.");
 39              }
 40  
 41              var checksum = (BgcodeChecksumType)reader.ReadUInt16();
 42  
 43              while (reader.BaseStream.Position < reader.BaseStream.Length)
 44              {
 45                  var blockType = (BgcodeBlockType)reader.ReadUInt16();
 46                  var compression = (BgcodeCompressionType)reader.ReadUInt16();
 47                  var uncompressedSize = reader.ReadUInt32();
 48  
 49                  var size = compression == BgcodeCompressionType.NoCompression ? uncompressedSize : reader.ReadUInt32();
 50  
 51                  switch (blockType)
 52                  {
 53                      case BgcodeBlockType.FileMetadataBlock:
 54                      case BgcodeBlockType.PrinterMetadataBlock:
 55                      case BgcodeBlockType.PrintMetadataBlock:
 56                      case BgcodeBlockType.SlicerMetadataBlock:
 57                      case BgcodeBlockType.GCodeBlock:
 58                          reader.BaseStream.Seek(2 + size, SeekOrigin.Current); // Skip
 59  
 60                          break;
 61  
 62                      case BgcodeBlockType.ThumbnailBlock:
 63                          var format = (BgcodeThumbnailFormat)reader.ReadUInt16();
 64  
 65                          reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip width and height
 66  
 67                          var data = ReadAndDecompressData(reader, compression, (int)size);
 68  
 69                          if (data != null)
 70                          {
 71                              yield return new BgcodeThumbnail(format, data);
 72                          }
 73  
 74                          break;
 75                  }
 76  
 77                  if (checksum == BgcodeChecksumType.CRC32)
 78                  {
 79                      reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip checksum
 80                  }
 81              }
 82          }
 83  
 84          /// <summary>
 85          /// Gets the best thumbnail available in a bgcode file.
 86          /// </summary>
 87          /// <param name="reader">The <see cref="BinaryReader"/> instance to the gcode file.</param>
 88          /// <returns>The best thumbnail available in the gcode file.</returns>
 89          public static BgcodeThumbnail? GetBestThumbnail(BinaryReader reader)
 90          {
 91              return GetThumbnails(reader)
 92                  .OrderByDescending(x => x.Format switch
 93                  {
 94                      BgcodeThumbnailFormat.PNG => 2,
 95                      BgcodeThumbnailFormat.QOI => 1,
 96                      BgcodeThumbnailFormat.JPG => 0,
 97                      _ => 0,
 98                  })
 99                  .ThenByDescending(x => x.Data.Length)
100                  .FirstOrDefault();
101          }
102  
103          private static byte[]? ReadAndDecompressData(BinaryReader reader, BgcodeCompressionType compression, int size)
104          {
105              // Though the spec doesn't actually mention it, the reference encoder code never applies compression to thumbnails data
106              // which makes complete sense as this data is PNG, JPEG or QOI encoded so already compressed as much as possible!
107              switch (compression)
108              {
109                  case BgcodeCompressionType.NoCompression:
110                      return reader.ReadBytes(size);
111  
112                  case BgcodeCompressionType.DeflateAlgorithm:
113                      var buffer = new byte[size];
114  
115                      using (var deflateStream = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true))
116                      {
117                          deflateStream.ReadExactly(buffer, 0, size);
118                      }
119  
120                      return buffer;
121  
122                  default:
123                      reader.BaseStream.Seek(size, SeekOrigin.Current); // Skip unknown or unsupported compression types
124  
125                      return null;
126              }
127          }
128      }
129  }