예제 #1
0
        /// <summary>
        /// This method will use User32 code to capture the specified captureBounds from the screen
        /// </summary>
        /// <param name="captureBounds">Rectangle with the bounds to capture</param>
        /// <returns>Bitmap which is captured from the screen at the location specified by the captureBounds</returns>
        public static Bitmap CaptureRectangle(Rectangle captureBounds)
        {
            Bitmap returnBitmap = null;

            if (captureBounds.Height <= 0 || captureBounds.Width <= 0)
            {
                LOG.Warn("Nothing to capture, ignoring!");
                return(null);
            }
            else
            {
                LOG.Debug("CaptureRectangle Called!");
            }
            // .NET GDI+ Solution, according to some post this has a GDI+ leak...
            // See http://connect.microsoft.com/VisualStudio/feedback/details/344752/gdi-object-leak-when-calling-graphics-copyfromscreen
            // Bitmap capturedBitmap = new Bitmap(captureBounds.Width, captureBounds.Height);
            // using (Graphics graphics = Graphics.FromImage(capturedBitmap)) {
            //	graphics.CopyFromScreen(captureBounds.Location, Point.Empty, captureBounds.Size, CopyPixelOperation.CaptureBlt);
            // }
            // capture.Image = capturedBitmap;
            // capture.Location = captureBounds.Location;

            using (SafeWindowDCHandle desktopDCHandle = SafeWindowDCHandle.fromDesktop()) {
                if (desktopDCHandle.IsInvalid)
                {
                    // Get Exception before the error is lost
                    Exception exceptionToThrow = CreateCaptureException("desktopDCHandle", captureBounds);
                    // throw exception
                    throw exceptionToThrow;
                }

                // create a device context we can copy to
                using (SafeCompatibleDCHandle safeCompatibleDCHandle = GDI32.CreateCompatibleDC(desktopDCHandle)) {
                    // Check if the device context is there, if not throw an error with as much info as possible!
                    if (safeCompatibleDCHandle.IsInvalid)
                    {
                        // Get Exception before the error is lost
                        Exception exceptionToThrow = CreateCaptureException("CreateCompatibleDC", captureBounds);
                        // throw exception
                        throw exceptionToThrow;
                    }
                    // Create BitmapInfoHeader for CreateDIBSection
                    BitmapInfoHeader bmi = new BitmapInfoHeader(captureBounds.Width, captureBounds.Height, 24);

                    // Make sure the last error is set to 0
                    Win32.SetLastError(0);

                    // create a bitmap we can copy it to, using GetDeviceCaps to get the width/height
                    IntPtr bits0;                     // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap.
                    using (SafeDibSectionHandle safeDibSectionHandle = GDI32.CreateDIBSection(desktopDCHandle, ref bmi, BitmapInfoHeader.DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0)) {
                        if (safeDibSectionHandle.IsInvalid)
                        {
                            // Get Exception before the error is lost
                            Exception exceptionToThrow = CreateCaptureException("CreateDIBSection", captureBounds);
                            exceptionToThrow.Data.Add("hdcDest", safeCompatibleDCHandle.DangerousGetHandle().ToInt32());
                            exceptionToThrow.Data.Add("hdcSrc", desktopDCHandle.DangerousGetHandle().ToInt32());

                            // Throw so people can report the problem
                            throw exceptionToThrow;
                        }
                        else
                        {
                            // select the bitmap object and store the old handle
                            using (SafeSelectObjectHandle selectObject = safeCompatibleDCHandle.SelectObject(safeDibSectionHandle)) {
                                // bitblt over (make copy)
                                GDI32.BitBlt(safeCompatibleDCHandle, 0, 0, captureBounds.Width, captureBounds.Height, desktopDCHandle, captureBounds.X, captureBounds.Y, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
                            }

                            // get a .NET image object for it
                            // A suggestion for the "A generic error occurred in GDI+." E_FAIL/0×80004005 error is to re-try...
                            bool success = false;
                            ExternalException exception = null;
                            for (int i = 0; i < 3; i++)
                            {
                                try {
                                    // Collect all screens inside this capture
                                    List <Screen> screensInsideCapture = new List <Screen>();
                                    foreach (Screen screen in Screen.AllScreens)
                                    {
                                        if (screen.Bounds.IntersectsWith(captureBounds))
                                        {
                                            screensInsideCapture.Add(screen);
                                        }
                                    }
                                    // Check all all screens are of an equal size
                                    bool offscreenContent = false;
                                    using (Region captureRegion = new Region(captureBounds)) {
                                        // Exclude every visible part
                                        foreach (Screen screen in screensInsideCapture)
                                        {
                                            captureRegion.Exclude(screen.Bounds);
                                        }
                                        // If the region is not empty, we have "offscreenContent"
                                        using (Graphics screenGraphics = Graphics.FromHwnd(User32.GetDesktopWindow())) {
                                            offscreenContent = !captureRegion.IsEmpty(screenGraphics);
                                        }
                                    }
                                    // Check if we need to have a transparent background, needed for offscreen content
                                    if (offscreenContent)
                                    {
                                        using (Bitmap tmpBitmap = Bitmap.FromHbitmap(safeDibSectionHandle.DangerousGetHandle())) {
                                            // Create a new bitmap which has a transparent background
                                            returnBitmap = ImageHelper.CreateEmpty(tmpBitmap.Width, tmpBitmap.Height, PixelFormat.Format32bppArgb, Color.Transparent, tmpBitmap.HorizontalResolution, tmpBitmap.VerticalResolution);
                                            // Content will be copied here
                                            using (Graphics graphics = Graphics.FromImage(returnBitmap)) {
                                                // For all screens copy the content to the new bitmap
                                                foreach (Screen screen in Screen.AllScreens)
                                                {
                                                    Rectangle screenBounds = screen.Bounds;
                                                    // Make sure the bounds are offsetted to the capture bounds
                                                    screenBounds.Offset(-captureBounds.X, -captureBounds.Y);
                                                    graphics.DrawImage(tmpBitmap, screenBounds, screenBounds.X, screenBounds.Y, screenBounds.Width, screenBounds.Height, GraphicsUnit.Pixel);
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        // All screens, which are inside the capture, are of equal size
                                        // assign image to Capture, the image will be disposed there..
                                        returnBitmap = Bitmap.FromHbitmap(safeDibSectionHandle.DangerousGetHandle());
                                    }
                                    // We got through the capture without exception
                                    success = true;
                                    break;
                                } catch (ExternalException ee) {
                                    LOG.Warn("Problem getting bitmap at try " + i + " : ", ee);
                                    exception = ee;
                                }
                            }
                            if (!success)
                            {
                                LOG.Error("Still couldn't create Bitmap!");
                                throw exception;
                            }
                        }
                    }
                }
            }
            return(returnBitmap);
        }
예제 #2
0
        /// <summary>
        /// Write the images to the stream as icon
        /// Every image is resized to 256x256 (but the content maintains the aspect ratio)
        /// </summary>
        /// <param name="stream">Stream to write to</param>
        /// <param name="images">List of images</param>
        public static void WriteIcon(Stream stream, IList <Image> 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 <Size>         imageSizes    = new List <Size>();
            IList <MemoryStream> encodedImages = new List <MemoryStream>();

            foreach (var image in images)
            {
                // Pick the best fit
                var sizes = new[] { 16, 32, 48 };
                int size  = 256;
                foreach (var possibleSize in sizes)
                {
                    if (image.Width <= possibleSize && image.Height <= possibleSize)
                    {
                        size = possibleSize;
                        break;
                    }
                }
                var imageStream = new MemoryStream();
                if (image.Width == size && image.Height == size)
                {
                    using (var clonedImage = ImageHelper.Clone(image, PixelFormat.Format32bppArgb))
                    {
                        clonedImage.Save(imageStream, ImageFormat.Png);
                        imageSizes.Add(new Size(size, size));
                    }
                }
                else
                {
                    // Resize to the specified size, 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, size, size, 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();
            }
        }
예제 #3
0
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="imageToSave">image to save</param>
        /// <param name="surface">surface for the elements, needed if the greenshot format is used</param>
        /// <param name="stream">Stream to save to</param>
        /// <param name="outputSettings">SurfaceOutputSettings</param>
        public static void SaveToStream(Image imageToSave, ISurface surface, Stream stream, SurfaceOutputSettings outputSettings)
        {
            bool         useMemoryStream = false;
            MemoryStream memoryStream    = null;

            if (outputSettings.Format == OutputFormat.greenshot && surface == null)
            {
                throw new ArgumentException("Surface needs to be set when using OutputFormat.Greenshot");
            }

            try {
                ImageFormat imageFormat;
                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.ico:
                    imageFormat = ImageFormat.Icon;
                    break;

                default:
                    imageFormat = ImageFormat.Png;
                    break;
                }
                Log.DebugFormat("Saving image to stream with Format {0} and PixelFormat {1}", imageFormat, imageToSave.PixelFormat);

                // Check if we want to use a memory stream, to prevent issues with non seakable streams
                // The save is made to the targetStream, this is directed to either the MemoryStream or the original
                Stream targetStream = stream;
                if (!stream.CanSeek)
                {
                    useMemoryStream = true;
                    Log.Warn("Using memorystream prevent an issue with saving to a non seekable stream.");
                    memoryStream = new MemoryStream();
                    targetStream = memoryStream;
                }

                if (Equals(imageFormat, ImageFormat.Jpeg))
                {
                    bool foundEncoder = false;
                    foreach (ImageCodecInfo imageCodec in ImageCodecInfo.GetImageEncoders())
                    {
                        if (imageCodec.FormatID == imageFormat.Guid)
                        {
                            EncoderParameters parameters = new EncoderParameters(1)
                            {
                                Param = { [0] = new EncoderParameter(Encoder.Quality, outputSettings.JPGQuality) }
                            };
                            // 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();
                            }
                            else
                            {
                                AddTag(imageToSave);
                                imageToSave.Save(targetStream, imageCodec, parameters);
                            }
                            foundEncoder = true;
                            break;
                        }
                    }
                    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 <Image> images = new List <Image>();
                    images.Add(imageToSave);
                    WriteIcon(stream, images);
                }
                else
                {
                    bool needsDispose = false;
                    // Removing transparency if it's not supported in the output
                    if (!Equals(imageFormat, ImageFormat.Png) && Image.IsAlphaPixelFormat(imageToSave.PixelFormat))
                    {
                        imageToSave  = ImageHelper.Clone(imageToSave, PixelFormat.Format24bppRgb);
                        needsDispose = true;
                    }
                    AddTag(imageToSave);
                    // Added for OptiPNG
                    bool processed = false;
                    if (Equals(imageFormat, ImageFormat.Png) && !string.IsNullOrEmpty(CoreConfig.OptimizePNGCommand))
                    {
                        processed = ProcessPngImageExternally(imageToSave, targetStream);
                    }
                    if (!processed)
                    {
                        imageToSave.Save(targetStream, imageFormat);
                    }
                    if (needsDispose)
                    {
                        imageToSave.Dispose();
                    }
                }

                // If we used a memory stream, we need to stream the memory stream to the original stream.
                if (useMemoryStream)
                {
                    memoryStream.WriteTo(stream);
                }

                // Output the surface elements, size and marker to the stream
                if (outputSettings.Format != OutputFormat.greenshot)
                {
                    return;
                }
                using (MemoryStream tmpStream = new MemoryStream()) {
                    long bytesWritten = surface.SaveElementsToStream(tmpStream);
                    using (BinaryWriter writer = new BinaryWriter(tmpStream)) {
                        writer.Write(bytesWritten);
                        Version v      = Assembly.GetExecutingAssembly().GetName().Version;
                        byte[]  marker = Encoding.ASCII.GetBytes($"Greenshot{v.Major:00}.{v.Minor:00}");
                        writer.Write(marker);
                        tmpStream.WriteTo(stream);
                    }
                }
            }
            finally
            {
                memoryStream?.Dispose();
            }
        }
예제 #4
0
        /// <summary>
        /// Create an image from a surface with the settings from the output settings applied
        /// </summary>
        /// <param name="surface"></param>
        /// <param name="outputSettings"></param>
        /// <param name="imageToSave"></param>
        /// <returns>true if the image must be disposed</returns>
        public static bool CreateImageFromSurface(ISurface surface, SurfaceOutputSettings outputSettings, out Image imageToSave)
        {
            bool disposeImage = false;

            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)
            {
                return(disposeImage);
            }
            Image tmpImage;

            if (outputSettings.Effects != null && outputSettings.Effects.Count > 0)
            {
                // apply effects, if there are any
                using (Matrix matrix = new Matrix()) {
                    tmpImage = ImageHelper.ApplyEffects(imageToSave, outputSettings.Effects, matrix);
                }
                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 || (!CoreConfig.OutputFileAutoReduceColors && !outputSettings.ReduceColors))
            {
                return(disposeImage);
            }
            bool isAlpha = Image.IsAlphaPixelFormat(imageToSave.PixelFormat);

            if (outputSettings.ReduceColors || (!isAlpha && CoreConfig.OutputFileAutoReduceColors))
            {
                using (var 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)
                    {
                        return(disposeImage);
                    }
                    try {
                        Log.Info("Reducing colors on bitmap to 256.");
                        tmpImage = quantizer.GetQuantizedImage(CoreConfig.OutputFileReduceColorsTo);
                        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);
                    }
                }
            }
            else if (isAlpha && !outputSettings.ReduceColors)
            {
                Log.Info("Skipping 'optional' color reduction as the image has alpha");
            }
            return(disposeImage);
        }
예제 #5
0
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="imageToSave">image to save</param>
        /// <param name="surface">surface for the elements, needed if the greenshot format is used</param>
        /// <param name="stream">Stream to save to</param>
        /// <param name="outputSettings">SurfaceOutputSettings</param>
        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)
            {
                throw new ArgumentException("Surface needs to be se when using OutputFormat.Greenshot");
            }

            try
            {
                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;

                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;
                }
                LOG.DebugFormat("Saving image to stream with Format {0} and PixelFormat {1}", imageFormat, imageToSave.PixelFormat);

                // Check if we want to use a memory stream, to prevent a issue which happens with Windows before "7".
                // The save is made to the targetStream, this is directed to either the MemoryStream or the original
                Stream targetStream = stream;
                if (useMemoryStream)
                {
                    memoryStream = new MemoryStream();
                    targetStream = memoryStream;
                }

                if (Equals(imageFormat, ImageFormat.Jpeg))
                {
                    bool foundEncoder = false;
                    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))
                            {
                                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;
                        }
                    }
                    if (!foundEncoder)
                    {
                        throw new ApplicationException("No JPG encoder found, this should not happen.");
                    }
                }
                else
                {
                    bool needsDispose = false;
                    // Removing transparency if it's not supported in the output
                    if (!Equals(imageFormat, ImageFormat.Png) && Image.IsAlphaPixelFormat(imageToSave.PixelFormat))
                    {
                        imageToSave  = ImageHelper.Clone(imageToSave, PixelFormat.Format24bppRgb);
                        needsDispose = true;
                    }
                    AddTag(imageToSave);
                    // Added for OptiPNG
                    bool processed = false;
                    if (Equals(imageFormat, ImageFormat.Png) && !string.IsNullOrEmpty(conf.OptimizePNGCommand))
                    {
                        processed = ProcessPNGImageExternally(imageToSave, targetStream);
                    }
                    if (!processed)
                    {
                        imageToSave.Save(targetStream, imageFormat);
                    }
                    if (needsDispose)
                    {
                        imageToSave.Dispose();
                        imageToSave = null;
                    }
                }

                // If we used a memory stream, we need to stream the memory stream to the original stream.
                if (useMemoryStream)
                {
                    memoryStream.WriteTo(stream);
                }

                // Output the surface elements, size and marker to the stream
                if (outputSettings.Format == OutputFormat.greenshot)
                {
                    using (MemoryStream tmpStream = new MemoryStream())
                    {
                        long bytesWritten = surface.SaveElementsToStream(tmpStream);
                        using (BinaryWriter writer = new BinaryWriter(tmpStream))
                        {
                            writer.Write(bytesWritten);
                            Version v      = Assembly.GetExecutingAssembly().GetName().Version;
                            byte[]  marker = Encoding.ASCII.GetBytes(String.Format("Greenshot{0:00}.{1:00}", v.Major, v.Minor));
                            writer.Write(marker);
                            tmpStream.WriteTo(stream);
                        }
                    }
                }
            }
            finally
            {
                if (memoryStream != null)
                {
                    memoryStream.Dispose();
                }
            }
        }