diff --git a/GreenshotPlugin/Core/CoreConfiguration.cs b/GreenshotPlugin/Core/CoreConfiguration.cs index f7361aadb..68ffe9a5b 100644 --- a/GreenshotPlugin/Core/CoreConfiguration.cs +++ b/GreenshotPlugin/Core/CoreConfiguration.cs @@ -34,7 +34,7 @@ namespace GreenshotPlugin.Core { PNG, DIB, HTML, HTMLDATAURL, BITMAP, DIBV5 } public enum OutputFormat { - bmp, gif, jpg, png, tiff, greenshot + bmp, gif, jpg, png, tiff, greenshot, ico } public enum WindowCaptureMode { Screen, GDI, Aero, AeroTransparent, Auto diff --git a/GreenshotPlugin/Core/ImageHelper.cs b/GreenshotPlugin/Core/ImageHelper.cs index 514256986..1219fe1a5 100644 --- a/GreenshotPlugin/Core/ImageHelper.cs +++ b/GreenshotPlugin/Core/ImageHelper.cs @@ -1345,7 +1345,7 @@ namespace GreenshotPlugin.Core { /// /// Image to scale /// true to maintain the aspect ratio - /// + /// Makes the image maintain aspect ratio, but the canvas get's the specified size /// The color to fill with, or Color.Empty to take the default depending on the pixel format /// new width /// new height diff --git a/GreenshotPlugin/Core/ImageOutput.cs b/GreenshotPlugin/Core/ImageOutput.cs index e0ee6c801..64f6709ff 100644 --- a/GreenshotPlugin/Core/ImageOutput.cs +++ b/GreenshotPlugin/Core/ImageOutput.cs @@ -24,6 +24,7 @@ using Greenshot.Plugin; using GreenshotPlugin.Controls; using log4net; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; @@ -101,7 +102,6 @@ namespace GreenshotPlugin.Core { /// Stream to save to /// SurfaceOutputSettings public static void SaveToStream(Image imageToSave, ISurface surface, Stream stream, SurfaceOutputSettings outputSettings) { - ImageFormat imageFormat; bool useMemoryStream = false; MemoryStream memoryStream = null; if (outputSettings.Format == OutputFormat.greenshot && surface == null) { @@ -109,6 +109,7 @@ namespace GreenshotPlugin.Core { } try { + ImageFormat imageFormat; switch (outputSettings.Format) { case OutputFormat.bmp: imageFormat = ImageFormat.Bmp; @@ -122,13 +123,15 @@ namespace GreenshotPlugin.Core { case OutputFormat.tiff: imageFormat = ImageFormat.Tiff; break; + case OutputFormat.ico: + imageFormat = ImageFormat.Icon; + break; default: // Problem with non-seekable streams most likely doesn't happen with Windows 7 (OS Version 6.1 and later) // http://stackoverflow.com/questions/8349260/generic-gdi-error-on-one-machine-but-not-the-other if (!stream.CanSeek) { - int majorVersion = Environment.OSVersion.Version.Major; - int minorVersion = Environment.OSVersion.Version.Minor; - if (majorVersion < 6 || (majorVersion == 6 && minorVersion == 0)) { + if (!Environment.OSVersion.IsWindows7OrLater()) + { useMemoryStream = true; LOG.Warn("Using memorystream prevent an issue with saving to a non seekable stream."); } @@ -146,20 +149,26 @@ namespace GreenshotPlugin.Core { targetStream = memoryStream; } - if (Equals(imageFormat, ImageFormat.Jpeg)) { + if (Equals(imageFormat, ImageFormat.Jpeg)) + { bool foundEncoder = false; - foreach (ImageCodecInfo imageCodec in ImageCodecInfo.GetImageEncoders()) { - if (imageCodec.FormatID == imageFormat.Guid) { + foreach (ImageCodecInfo imageCodec in ImageCodecInfo.GetImageEncoders()) + { + if (imageCodec.FormatID == imageFormat.Guid) + { EncoderParameters parameters = new EncoderParameters(1); parameters.Param[0] = new EncoderParameter(Encoder.Quality, outputSettings.JPGQuality); // Removing transparency if it's not supported in the output - if (Image.IsAlphaPixelFormat(imageToSave.PixelFormat)) { + if (Image.IsAlphaPixelFormat(imageToSave.PixelFormat)) + { Image nonAlphaImage = ImageHelper.Clone(imageToSave, PixelFormat.Format24bppRgb); AddTag(nonAlphaImage); nonAlphaImage.Save(targetStream, imageCodec, parameters); nonAlphaImage.Dispose(); nonAlphaImage = null; - } else { + } + else + { AddTag(imageToSave); imageToSave.Save(targetStream, imageCodec, parameters); } @@ -167,9 +176,15 @@ namespace GreenshotPlugin.Core { break; } } - if (!foundEncoder) { + if (!foundEncoder) + { throw new ApplicationException("No JPG encoder found, this should not happen."); } + } else if (Equals(imageFormat, ImageFormat.Icon)) { + // FEATURE-916: Added Icon support + IList images = new List(); + images.Add(imageToSave); + WriteIcon(stream, images); } else { bool needsDispose = false; // Removing transparency if it's not supported in the output @@ -181,7 +196,7 @@ namespace GreenshotPlugin.Core { // Added for OptiPNG bool processed = false; if (Equals(imageFormat, ImageFormat.Png) && !string.IsNullOrEmpty(conf.OptimizePNGCommand)) { - processed = ProcessPNGImageExternally(imageToSave, targetStream); + processed = ProcessPngImageExternally(imageToSave, targetStream); } if (!processed) { imageToSave.Save(targetStream, imageFormat); @@ -223,7 +238,7 @@ namespace GreenshotPlugin.Core { /// Image to pass to the external process /// stream to write the processed image to /// - private static bool ProcessPNGImageExternally(Image imageToProcess, Stream targetStream) { + private static bool ProcessPngImageExternally(Image imageToProcess, Stream targetStream) { if (string.IsNullOrEmpty(conf.OptimizePNGCommand)) { return false; } @@ -244,12 +259,14 @@ namespace GreenshotPlugin.Core { LOG.DebugFormat("Starting : {0}", conf.OptimizePNGCommand); } - ProcessStartInfo processStartInfo = new ProcessStartInfo(conf.OptimizePNGCommand); - processStartInfo.Arguments = string.Format(conf.OptimizePNGCommandArguments, tmpFileName); - processStartInfo.CreateNoWindow = true; - processStartInfo.RedirectStandardOutput = true; - processStartInfo.RedirectStandardError = true; - processStartInfo.UseShellExecute = false; + ProcessStartInfo processStartInfo = new ProcessStartInfo(conf.OptimizePNGCommand) + { + Arguments = string.Format(conf.OptimizePNGCommandArguments, tmpFileName), + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; using (Process process = Process.Start(processStartInfo)) { if (process != null) { process.WaitForExit(); @@ -450,7 +467,7 @@ namespace GreenshotPlugin.Core { /// OutputFormat public static OutputFormat FormatForFilename(string fullPath) { // Fix for bug 2912959 - string extension = fullPath.Substring(fullPath.LastIndexOf(".") + 1); + string extension = fullPath.Substring(fullPath.LastIndexOf(".", StringComparison.Ordinal) + 1); OutputFormat format = OutputFormat.png; try { format = (OutputFormat)Enum.Parse(typeof(OutputFormat), extension.ToLower()); @@ -601,5 +618,77 @@ namespace GreenshotPlugin.Core { File.Delete(path); } } + + #region Icon + + /// + /// Write the images to the stream as icon + /// Every image is resized to 256x256 (but the content maintains the aspect ratio) + /// + /// Stream to write to + /// List of images + public static void WriteIcon(Stream stream, IList images) + { + var binaryWriter = new BinaryWriter(stream); + // + // ICONDIR structure + // + binaryWriter.Write((short)0); // reserved + binaryWriter.Write((short)1); // image type (icon) + binaryWriter.Write((short)images.Count); // number of images + + IList imageSizes = new List(); + IList encodedImages = new List(); + foreach (var image in images) + { + var imageStream = new MemoryStream(); + // Always size to 256x256, first make sure the image is 32bpp + using (var clonedImage = ImageHelper.Clone(image, PixelFormat.Format32bppArgb)) + { + using (var resizedImage = ImageHelper.ResizeImage(clonedImage, true, true, Color.Empty, 256, 256, null)) + { + resizedImage.Save(imageStream, ImageFormat.Png); + imageSizes.Add(resizedImage.Size); + } + + } + imageStream.Seek(0, SeekOrigin.Begin); + encodedImages.Add(imageStream); + } + + // + // ICONDIRENTRY structure + // + const int iconDirSize = 6; + const int iconDirEntrySize = 16; + + var offset = iconDirSize + (images.Count * iconDirEntrySize); + for (int i = 0; i < images.Count; i++) + { + var imageSize = imageSizes[i]; + // Write the width / height, 0 means 256 + binaryWriter.Write(imageSize.Width == 256 ? (byte)0 : (byte)imageSize.Width); + binaryWriter.Write(imageSize.Height == 256 ? (byte)0 : (byte)imageSize.Height); + binaryWriter.Write((byte)0); // no pallete + binaryWriter.Write((byte)0); // reserved + binaryWriter.Write((short)0); // no color planes + binaryWriter.Write((short)32); // 32 bpp + binaryWriter.Write((int)encodedImages[i].Length); // image data length + binaryWriter.Write(offset); + offset += (int)encodedImages[i].Length; + } + + binaryWriter.Flush(); + // + // Write image data + // + foreach (var encodedImage in encodedImages) + { + encodedImage.WriteTo(stream); + encodedImage.Dispose(); + } + } + #endregion + } }