// 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.Drawing.Drawing2D; using System.Text; namespace Microsoft.PowerToys.ThumbnailHandler.Gcode { /// /// G-code Thumbnail Provider. /// public class GcodeThumbnailProvider { public GcodeThumbnailProvider(string filePath) { FilePath = filePath; Stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); } /// /// Gets the file path to the file creating thumbnail for. /// public string FilePath { get; private set; } /// /// Gets the stream object to access file. /// public Stream Stream { get; private set; } /// /// The maximum dimension (width or height) thumbnail we will generate. /// private const uint MaxThumbnailSize = 10000; /// /// Reads the G-code content searching for thumbnails and returns the largest. /// /// The TextReader instance for the G-code content. /// The maximum thumbnail size, in pixels. /// A thumbnail extracted from the G-code content. public static Bitmap GetThumbnail(TextReader reader, uint cx) { if (cx > MaxThumbnailSize || 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)); 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); } } return thumbnail; } /// /// Gets all thumbnails in base64 format found on the G-code data. /// /// The TextReader instance for the G-code content. /// An enumeration of thumbnails in base64 format found on the G-code. private static IEnumerable 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..]); } } } /// /// Resize the image with high quality to the specified width and height. /// /// The image to resize. /// The width to resize to. /// The height to resize to. /// The resized image. public static Bitmap ResizeImage(Image image, int width, int height) { if (width <= 0 || height <= 0 || width > MaxThumbnailSize || height > MaxThumbnailSize || image == null) { return null; } Bitmap destImage = new Bitmap(width, height); destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); using (var graphics = Graphics.FromImage(destImage)) { graphics.CompositingMode = CompositingMode.SourceCopy; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.DrawImage(image, 0, 0, width, height); } return destImage; } /// /// Generate thumbnail bitmap for provided Gcode file/stream. /// /// Maximum thumbnail size, in pixels. /// Generated bitmap public Bitmap GetThumbnail(uint cx) { if (cx == 0 || cx > MaxThumbnailSize) { return null; } if (global::PowerToys.GPOWrapper.GPOWrapper.GetConfiguredGcodeThumbnailsEnabledValue() == global::PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { // GPO is disabling this utility. return null; } using (var reader = new StreamReader(this.Stream)) { using (Bitmap thumbnail = GetThumbnail(reader, cx)) { if (thumbnail != null && thumbnail.Size.Width > 0 && thumbnail.Size.Height > 0) { return (Bitmap)thumbnail.Clone(); } } } return null; } } }