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 }