diff --git a/GreenshotPlugin/Core/ClipboardHelper.cs b/GreenshotPlugin/Core/ClipboardHelper.cs index 6b5b47566..47ddf210b 100644 --- a/GreenshotPlugin/Core/ClipboardHelper.cs +++ b/GreenshotPlugin/Core/ClipboardHelper.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.Drawing.Imaging; using System.IO; using System.Text; using System.Threading; @@ -311,25 +312,43 @@ EndSelection:<<<<<<<4 try { MemoryStream imageStream = null; - if (!isValidStream(imageStream) && formats.Contains(FORMAT_PNG)) { + if (formats.Contains(FORMAT_PNG)) { imageStream = GetFromDataObject(dataObject, FORMAT_PNG) as MemoryStream; - } - if (!isValidStream(imageStream) && formats.Contains(FORMAT_JPG)) { - imageStream = GetFromDataObject(dataObject, FORMAT_JPG) as MemoryStream; - } - if (!isValidStream(imageStream) && formats.Contains(DataFormats.Tiff)) { - imageStream = GetFromDataObject(dataObject, DataFormats.Tiff) as MemoryStream; - } - - if (isValidStream(imageStream)) { - try { - using (Image tmpImage = Image.FromStream(imageStream)) { - return ImageHelper.Clone(tmpImage); + if (isValidStream(imageStream)) { + try { + using (Image tmpImage = Image.FromStream(imageStream)) { + return ImageHelper.Clone(tmpImage); + } + } catch (Exception streamImageEx) { + LOG.Error("Problem retrieving PNG image from clipboard.", streamImageEx); } - } catch (Exception streamImageEx) { - LOG.Error("Problem retrieving Image from clipboard.", streamImageEx); } } + if (formats.Contains(FORMAT_JPG)) { + imageStream = GetFromDataObject(dataObject, FORMAT_JPG) as MemoryStream; + if (isValidStream(imageStream)) { + try { + using (Image tmpImage = Image.FromStream(imageStream)) { + return ImageHelper.Clone(tmpImage); + } + } catch (Exception streamImageEx) { + LOG.Error("Problem retrieving JPG image from clipboard.", streamImageEx); + } + } + } + if (formats.Contains(DataFormats.Tiff)) { + imageStream = GetFromDataObject(dataObject, DataFormats.Tiff) as MemoryStream; + if (isValidStream(imageStream)) { + try { + using (Image tmpImage = Image.FromStream(imageStream)) { + return ImageHelper.Clone(tmpImage); + } + } catch (Exception streamImageEx) { + LOG.Error("Problem retrieving TIFF image from clipboard.", streamImageEx); + } + } + } + // the DIB readed should solve the issue reported here: https://sourceforge.net/projects/greenshot/forums/forum/676083/topic/6354353/index/page/1 try { // If the EnableSpecialDIBClipboardReader flag in the config is set, use the code from: @@ -466,14 +485,19 @@ EndSelection:<<<<<<<4 MemoryStream dibStream = null; MemoryStream pngStream = null; + Image imageToSave = null; + bool disposeImage = false; try { + SurfaceOutputSettings outputSettings = new SurfaceOutputSettings(OutputFormat.png, 100, false); + // Create the image which is going to be saved so we don't create it multiple times + disposeImage = ImageOutput.CreateImageFromSurface(surface, outputSettings, out imageToSave); try { // Create PNG stream if (config.ClipboardFormats.Contains(ClipboardFormat.PNG)) { pngStream = new MemoryStream(); // PNG works for e.g. Powerpoint SurfaceOutputSettings pngOutputSettings = new SurfaceOutputSettings(OutputFormat.png, 100, false); - ImageOutput.SaveToStream(surface, pngStream, pngOutputSettings); + ImageOutput.SaveToStream(imageToSave, null, pngStream, pngOutputSettings); pngStream.Seek(0, SeekOrigin.Begin); // Set the PNG stream ido.SetData(FORMAT_PNG, false, pngStream); @@ -482,13 +506,12 @@ EndSelection:<<<<<<<4 LOG.Error("Error creating PNG for the Clipboard.", pngEX); } - try { if (config.ClipboardFormats.Contains(ClipboardFormat.DIB)) { using (MemoryStream tmpBmpStream = new MemoryStream()) { // Save image as BMP SurfaceOutputSettings bmpOutputSettings = new SurfaceOutputSettings(OutputFormat.bmp, 100, false); - ImageOutput.SaveToStream(surface, tmpBmpStream, bmpOutputSettings); + ImageOutput.SaveToStream(imageToSave, null, tmpBmpStream, bmpOutputSettings); dibStream = new MemoryStream(); // Copy the source, but skip the "BITMAPFILEHEADER" which has a size of 14 @@ -514,7 +537,12 @@ EndSelection:<<<<<<<4 // Do not allow to reduce the colors, some applications dislike 256 color images // reported with bug #3594681 pngOutputSettings.DisableReduceColors = true; - ImageOutput.SaveToStream(surface, tmpPNGStream, pngOutputSettings); + // Check if we can use the previously used image + if (imageToSave.PixelFormat != PixelFormat.Format8bppIndexed) { + ImageOutput.SaveToStream(imageToSave, surface, tmpPNGStream, pngOutputSettings); + } else { + ImageOutput.SaveToStream(surface, tmpPNGStream, pngOutputSettings); + } html = getHTMLDataURLString(surface, tmpPNGStream); } ido.SetText(html, TextDataFormat.Html); @@ -523,11 +551,9 @@ EndSelection:<<<<<<<4 // we need to use the SetDataOject before the streams are closed otherwise the buffer will be gone! // Check if Bitmap is wanted if (config.ClipboardFormats.Contains(ClipboardFormat.BITMAP)) { - using (Image tmpImage = surface.GetImageForExport()) { - ido.SetImage(tmpImage); - // Place the DataObject to the clipboard - SetDataObject(ido, true); - } + ido.SetImage(imageToSave); + // Place the DataObject to the clipboard + SetDataObject(ido, true); } else { // Place the DataObject to the clipboard SetDataObject(ido, true); @@ -542,6 +568,10 @@ EndSelection:<<<<<<<4 dibStream.Dispose(); dibStream = null; } + // cleanup if needed + if (disposeImage && imageToSave != null) { + imageToSave.Dispose(); + } } } diff --git a/GreenshotPlugin/Core/ImageOutput.cs b/GreenshotPlugin/Core/ImageOutput.cs index 7f710af4c..2fa7a4808 100644 --- a/GreenshotPlugin/Core/ImageOutput.cs +++ b/GreenshotPlugin/Core/ImageOutput.cs @@ -69,117 +69,65 @@ namespace GreenshotPlugin.Core { return propertyItem; } #region save + /// + /// Saves ISurface to stream with specified output settings + /// + /// ISurface to save + /// Stream to save to + /// SurfaceOutputSettings + public static void SaveToStream(ISurface surface, Stream stream, SurfaceOutputSettings outputSettings) { + Image imageToSave = null; + bool disposeImage = CreateImageFromSurface(surface, outputSettings, out imageToSave); + SaveToStream(imageToSave, surface, stream, outputSettings); + // cleanup if needed + if (disposeImage && imageToSave != null) { + imageToSave.Dispose(); + } + } /// /// Saves image to stream with specified quality /// To prevent problems with GDI version of before Windows 7: /// the stream is checked if it's seekable and if needed a MemoryStream as "cache" is used. /// - public static void SaveToStream(ISurface surface, Stream stream, SurfaceOutputSettings outputSettings) { + /// image to save + /// surface for the elements, if the greenshot format is used + /// Stream to save to + /// SurfaceOutputSettings + public static void SaveToStream(Image imageToSave, ISurface surface, Stream stream, SurfaceOutputSettings outputSettings) { ImageFormat imageFormat = null; - bool disposeImage = false; bool useMemoryStream = false; MemoryStream memoryStream = null; - switch (outputSettings.Format) { - case OutputFormat.bmp: - imageFormat = ImageFormat.Bmp; - break; - case OutputFormat.gif: - imageFormat = ImageFormat.Gif; - break; - case OutputFormat.jpg: - imageFormat = ImageFormat.Jpeg; - break; - case OutputFormat.tiff: - imageFormat = ImageFormat.Tiff; - break; - case OutputFormat.greenshot: - case OutputFormat.png: - 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)) { - useMemoryStream = true; - LOG.Warn("Using memorystream prevent an issue with saving to a non seekable stream."); - } - } - imageFormat = ImageFormat.Png; - break; - } - - // check what image we want to save - Image imageToSave = null; - if (outputSettings.Format == OutputFormat.greenshot || outputSettings.SaveBackgroundOnly) { - // We save the image of the surface, this should not be disposed - imageToSave = surface.Image; - } else { - // We create the export image of the surface to save - imageToSave = surface.GetImageForExport(); - disposeImage = true; - } - try { - // The following block of modifications should be skipped when saving the greenshot format, no effects or otherwise! - if (outputSettings.Format != OutputFormat.greenshot) { - Image tmpImage; - if (outputSettings.Effects != null && outputSettings.Effects.Count > 0) { - // apply effects, if there are any - Point ignoreOffset; - tmpImage = ImageHelper.ApplyEffects((Bitmap)imageToSave, outputSettings.Effects, out ignoreOffset); - if (tmpImage != null) { - if (disposeImage) { - imageToSave.Dispose(); - } - imageToSave = tmpImage; - disposeImage = true; - } - } - - // Removing transparency if it's not supported in the output - if (imageFormat != ImageFormat.Png && Image.IsAlphaPixelFormat(imageToSave.PixelFormat)) { - Image nonAlphaImage = ImageHelper.Clone(imageToSave, PixelFormat.Format24bppRgb); - if (disposeImage) { - imageToSave.Dispose(); - } - // Make sure the image is disposed! - disposeImage = true; - imageToSave = nonAlphaImage; - } - - // check for color reduction, forced or automatically, only when the DisableReduceColors is false - if (!outputSettings.DisableReduceColors && (conf.OutputFileAutoReduceColors || outputSettings.ReduceColors)) { - WuQuantizer quantizer = new WuQuantizer((Bitmap)imageToSave); - int colorCount = quantizer.GetColorCount(); - LOG.InfoFormat("Image with format {0} has {1} colors", imageToSave.PixelFormat, colorCount); - if (outputSettings.ReduceColors || colorCount < 256) { - try { - LOG.Info("Reducing colors on bitmap to 255."); - tmpImage = quantizer.GetQuantizedImage(255); - if (disposeImage) { - imageToSave.Dispose(); - } - imageToSave = tmpImage; - // Make sure the "new" image is disposed - disposeImage = true; - } catch (Exception e) { - LOG.Warn("Error occurred while Quantizing the image, ignoring and using original. Error: ", e); + switch (outputSettings.Format) { + case OutputFormat.bmp: + imageFormat = ImageFormat.Bmp; + break; + case OutputFormat.gif: + imageFormat = ImageFormat.Gif; + break; + case OutputFormat.jpg: + imageFormat = ImageFormat.Jpeg; + break; + case OutputFormat.tiff: + imageFormat = ImageFormat.Tiff; + break; + case OutputFormat.greenshot: + case OutputFormat.png: + 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)) { + useMemoryStream = true; + LOG.Warn("Using memorystream prevent an issue with saving to a non seekable stream."); } } - } - - // Create meta-data - PropertyItem softwareUsedPropertyItem = CreatePropertyItem(PROPERTY_TAG_SOFTWARE_USED, "Greenshot"); - if (softwareUsedPropertyItem != null) { - try { - imageToSave.SetPropertyItem(softwareUsedPropertyItem); - } catch (ArgumentException) { - LOG.WarnFormat("Image of type {0} do not support property {1}", imageFormat, softwareUsedPropertyItem.Id); - } - } + imageFormat = ImageFormat.Png; + break; } LOG.DebugFormat("Saving image to stream with Format {0} and PixelFormat {1}", imageFormat, imageToSave.PixelFormat); @@ -197,7 +145,17 @@ namespace GreenshotPlugin.Core { if (imageCodec.FormatID == imageFormat.Guid) { EncoderParameters parameters = new EncoderParameters(1); parameters.Param[0] = new EncoderParameter(Encoder.Quality, outputSettings.JPGQuality); - imageToSave.Save(targetStream, imageCodec, parameters); + // Removing transparency if it's not supported in the output + if (Image.IsAlphaPixelFormat(imageToSave.PixelFormat)) { + Image nonAlphaImage = ImageHelper.Clone(imageToSave, PixelFormat.Format24bppRgb); + AddTag(nonAlphaImage); + nonAlphaImage.Save(targetStream, imageCodec, parameters); + nonAlphaImage.Dispose(); + nonAlphaImage = null; + } else { + AddTag(imageToSave); + imageToSave.Save(targetStream, imageCodec, parameters); + } foundEncoder = true; break; } @@ -206,7 +164,17 @@ namespace GreenshotPlugin.Core { throw new ApplicationException("No JPG encoder found, this should not happen."); } } else { - imageToSave.Save(targetStream, imageFormat); + // Removing transparency if it's not supported in the output + if (imageFormat != ImageFormat.Png && Image.IsAlphaPixelFormat(imageToSave.PixelFormat)) { + Image nonAlphaImage = ImageHelper.Clone(imageToSave, PixelFormat.Format24bppRgb); + AddTag(nonAlphaImage); + nonAlphaImage.Save(targetStream, imageFormat); + nonAlphaImage.Dispose(); + nonAlphaImage = null; + } else { + AddTag(imageToSave); + imageToSave.Save(targetStream, imageFormat); + } } // If we used a memory stream, we need to stream the memory stream to the original stream. @@ -231,9 +199,100 @@ namespace GreenshotPlugin.Core { if (memoryStream != null) { memoryStream.Dispose(); } - // cleanup if needed - if (disposeImage && imageToSave != null) { - imageToSave.Dispose(); + } + } + + /// + /// Create an image from a surface with the settings from the output settings applied + /// + /// + /// + /// + /// true if the image must be disposed + public static bool CreateImageFromSurface(ISurface surface, SurfaceOutputSettings outputSettings, out Image imageToSave) { + bool disposeImage = false; + ImageFormat imageFormat = null; + switch (outputSettings.Format) { + case OutputFormat.bmp: + imageFormat = ImageFormat.Bmp; + break; + case OutputFormat.gif: + imageFormat = ImageFormat.Gif; + break; + case OutputFormat.jpg: + imageFormat = ImageFormat.Jpeg; + break; + case OutputFormat.tiff: + imageFormat = ImageFormat.Tiff; + break; + case OutputFormat.greenshot: + case OutputFormat.png: + default: + imageFormat = ImageFormat.Png; + break; + } + + if (outputSettings.Format == OutputFormat.greenshot || outputSettings.SaveBackgroundOnly) { + // We save the image of the surface, this should not be disposed + imageToSave = surface.Image; + } else { + // We create the export image of the surface to save + imageToSave = surface.GetImageForExport(); + disposeImage = true; + } + + // The following block of modifications should be skipped when saving the greenshot format, no effects or otherwise! + if (outputSettings.Format != OutputFormat.greenshot) { + Image tmpImage; + if (outputSettings.Effects != null && outputSettings.Effects.Count > 0) { + // apply effects, if there are any + Point ignoreOffset; + tmpImage = ImageHelper.ApplyEffects((Bitmap)imageToSave, outputSettings.Effects, out ignoreOffset); + if (tmpImage != null) { + if (disposeImage) { + imageToSave.Dispose(); + } + imageToSave = tmpImage; + disposeImage = true; + } + } + + // check for color reduction, forced or automatically, only when the DisableReduceColors is false + if (!outputSettings.DisableReduceColors && (conf.OutputFileAutoReduceColors || outputSettings.ReduceColors)) { + WuQuantizer quantizer = new WuQuantizer((Bitmap)imageToSave); + int colorCount = quantizer.GetColorCount(); + LOG.InfoFormat("Image with format {0} has {1} colors", imageToSave.PixelFormat, colorCount); + if (outputSettings.ReduceColors || colorCount < 256) { + try { + LOG.Info("Reducing colors on bitmap to 255."); + tmpImage = quantizer.GetQuantizedImage(255); + if (disposeImage) { + imageToSave.Dispose(); + } + imageToSave = tmpImage; + // Make sure the "new" image is disposed + disposeImage = true; + } catch (Exception e) { + LOG.Warn("Error occurred while Quantizing the image, ignoring and using original. Error: ", e); + } + } + } + } + return disposeImage; + } + + /// + /// Add the greenshot property! + /// + /// + private static void AddTag(Image imageToSave) { + // Create meta-data + PropertyItem softwareUsedPropertyItem = CreatePropertyItem(PROPERTY_TAG_SOFTWARE_USED, "Greenshot"); + if (softwareUsedPropertyItem != null) { + try { + imageToSave.SetPropertyItem(softwareUsedPropertyItem); + } catch (Exception) { + LOG.WarnFormat("Couldn't set property {0}", softwareUsedPropertyItem.Id); } } }