From e860872ae0d5995ec7796fbd4682948f436df020 Mon Sep 17 00:00:00 2001 From: RKrom Date: Tue, 24 Jan 2012 19:28:12 +0000 Subject: [PATCH] Added some missing files. git-svn-id: http://svn.code.sf.net/p/greenshot/code/trunk@1603 7dccd23d-a4a3-4e1f-8c07-b4c1b4018ab4 --- Greenshot/Helpers/AviHelper.cs | 901 +++++++++++++++++++++++ Greenshot/Helpers/CaptureHelper.cs | 725 ++++++++++++++++++ Greenshot/Helpers/DestinationHelper.cs | 124 ++++ Greenshot/Helpers/GeometryHelper.cs | 63 ++ Greenshot/Helpers/ProcessorHelper.cs | 121 +++ Greenshot/Helpers/ScreenCaptureHelper.cs | 262 +++++++ Greenshot/Helpers/WindowWrapper.cs | 35 + 7 files changed, 2231 insertions(+) create mode 100644 Greenshot/Helpers/AviHelper.cs create mode 100644 Greenshot/Helpers/CaptureHelper.cs create mode 100644 Greenshot/Helpers/DestinationHelper.cs create mode 100644 Greenshot/Helpers/GeometryHelper.cs create mode 100644 Greenshot/Helpers/ProcessorHelper.cs create mode 100644 Greenshot/Helpers/ScreenCaptureHelper.cs create mode 100644 Greenshot/Helpers/WindowWrapper.cs diff --git a/Greenshot/Helpers/AviHelper.cs b/Greenshot/Helpers/AviHelper.cs new file mode 100644 index 000000000..224da01f0 --- /dev/null +++ b/Greenshot/Helpers/AviHelper.cs @@ -0,0 +1,901 @@ +// AForge Video for Windows Library +// AForge.NET framework +// http://www.aforgenet.com/framework/ +// +// Copyright © Andrew Kirillov, 2007-2009 +// andrew.kirillov@aforgenet.com +// +// +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; + +using GreenshotPlugin.UnmanagedHelpers; + +namespace Greenshot.Helpers { + + /// + /// AVI files writing using Video for Windows interface. + /// + /// + /// The class allows to write AVI files using Video for Windows API. + /// + /// Sample usage: + /// /// // instantiate AVI writer, use WMV3 codec + /// AVIWriter writer = new AVIWriter( "wmv3" ); + /// // create new AVI file and open it + /// writer.Open( "test.avi", 320, 240 ); + /// // create frame image + /// Bitmap image = new Bitmap( 320, 240 ); + /// + /// for ( int i = 0; i < 240; i++ ) + /// { + /// // update image + /// image.SetPixel( i, i, Color.Red ); + /// // add the image as a new frame of video file + /// writer.AddFrame( image ); + /// } + /// writer.Close( ); + /// + /// + /// + public class AVIWriter : IDisposable { + private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(AVIWriter)); + // AVI file + private IntPtr file; + // video stream + private IntPtr stream; + // compressed stream + private IntPtr streamCompressed; + // width of video frames + private int width; + // height of vide frames + private int height; + // length of one line + private int stride; + // quality + private int quality = -1; + // frame rate + private int rate = 25; + // current position + private int position; + // codec used for video compression + private string codec = null; //"DIB "; + + /// + /// Width of video frames. + /// + /// + /// The property specifies the width of video frames, which are acceptable + /// by method for saving, which is set in + /// method. + /// + public int Width { + get { return width; } + } + + /// + /// Height of video frames. + /// + /// + /// The property specifies the height of video frames, which are acceptable + /// by method for saving, which is set in + /// method. + /// + public int Height { + get { return height; } + } + + /// + /// Current position in video stream. + /// + /// + /// The property tell current position in video stream, which actually equals + /// to the amount of frames added using method. + /// + public int Position + { + get { return position; } + } + + /// + /// Desired playing frame rate. + /// + /// + /// The property sets the video frame rate, which should be use during playing + /// of the video to be saved. + /// + /// The property should be set befor opening new file to take effect. + /// + /// Default frame rate is set to 25. + /// + public int FrameRate + { + get { return rate; } + set { rate = value; } + } + + /// + /// Codec used for video compression. + /// + /// + /// The property sets the FOURCC code of video compression codec, which needs to + /// be used for video encoding. + /// + /// The property should be set befor opening new file to take effect. + /// + /// Default video codec is set "DIB ", which means no compression. + /// + public string Codec + { + get { return codec; } + set { codec = value; } + } + + /// + /// Compression video quality. + /// + /// + /// The property sets video quality used by codec in order to balance compression rate + /// and image quality. The quality is measured usually in the [0, 100] range. + /// + /// The property should be set befor opening new file to take effect. + /// + /// Default value is set to -1 - default compression quality of the codec. + /// + public int Quality + { + get { return quality; } + set { quality = value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Initializes Video for Windows library. + /// + public AVIWriter() { + Avi32.AVIFileInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Codec to use for compression. eg [CVID],[IV50] + /// + /// Initializes Video for Windows library. + /// + public AVIWriter(string codec) : this() { + this.codec = codec; + } + + /// + /// Destroys the instance of the class. + /// + /// + ~AVIWriter() { + Dispose(false); + } + + /// + /// Dispose the object. + /// + /// + /// Frees unmanaged resources used by the object. The object becomes unusable + /// after that. + /// + public void Dispose() { + Dispose(true); + // remove me from the Finalization queue + GC.SuppressFinalize(this); + } + + /// + /// Dispose the object. + /// + /// + /// Indicates if disposing was initiated manually. + /// + protected virtual void Dispose(bool disposing) { + if (disposing) { + // dispose managed resources + } + // close current AVI file if any opened and uninitialize AVI library + Close(); + Avi32.AVIFileExit(); + } + + /// + /// Create new AVI file and open it for writing. + /// + /// + /// AVI file name to create. + /// Video width. + /// Video height. + /// + /// The method opens (creates) a video files, configure video codec and prepares + /// the stream for saving video frames with a help of method. + /// + /// Failure of opening video files (the exception message + /// specifies the issues). + /// + public bool Open(string fileName, int width, int height) { + lock (this) { + // calculate stride + stride = width * 4; + if ((stride % 4) != 0) + stride += (4 - stride % 4); + + // create new file + if (Avi32.AVIFileOpen(out file, fileName, Avi32.OpenFileMode.Create | Avi32.OpenFileMode.Write, IntPtr.Zero) != 0) { + throw new ApplicationException("Failed opening file"); + } + + this.width = width; + this.height = height; + + // describe new stream + Avi32.AVISTREAMINFO info = new Avi32.AVISTREAMINFO(); + + info.type = Avi32.mmioFOURCC("vids"); + if (codec != null) { + info.handler = Avi32.mmioFOURCC(codec); + } else { + info.handler = Avi32.mmioFOURCC("DIB "); + } + info.scale = 1; + info.rate = rate; + info.suggestedBufferSize = stride * height; + + // create stream + if (Avi32.AVIFileCreateStream(file, out stream, ref info) != 0) { + throw new ApplicationException("Failed creating stream"); + } + + // describe compression options + Avi32.AVICOMPRESSOPTIONS options = new Avi32.AVICOMPRESSOPTIONS(); + + + // uncomment if video settings dialog is required to show + if (codec == null) { + int retCode = Avi32.AVISaveOptions( stream, ref options ); + if (retCode == 0) { + LOG.Debug("Cancel clicked!"); + return false; + } + } else { + options.handler = Avi32.mmioFOURCC(codec); + options.quality = quality; + } + + // create compressed stream + int retval = Avi32.AVIMakeCompressedStream(out streamCompressed, stream, ref options, IntPtr.Zero); + if (retval != 0) { + throw new ApplicationException("Failed creating compressed stream: " + retval); + } + + // describe frame format + BitmapInfoHeader bitmapInfoHeader = new BitmapInfoHeader(width, height, 32); + + // set frame format + retval = Avi32.AVIStreamSetFormat(streamCompressed, 0, ref bitmapInfoHeader, Marshal.SizeOf(bitmapInfoHeader.GetType())); + if (retval != 0) { + throw new ApplicationException("Failed creating compressed stream: "+ retval); + } + position = 0; + return true; + } + } + + /// + /// Close video file. + /// + /// + public void Close() { + LOG.Debug("Close called"); + lock (this) { + // release compressed stream + if (streamCompressed != IntPtr.Zero) { + LOG.Debug("AVIStreamRelease streamCompressed"); + Avi32.AVIStreamRelease(streamCompressed); + streamCompressed = IntPtr.Zero; + } + + // release stream + if (stream != IntPtr.Zero) { + LOG.Debug("AVIStreamRelease stream"); + Avi32.AVIStreamRelease(stream); + stream = IntPtr.Zero; + } + + // release file + if (file != IntPtr.Zero) { + LOG.Debug("AVIFileRelease file"); + Avi32.AVIFileRelease(file); + file = IntPtr.Zero; + } + } + } + + /// + /// Add new frame to the AVI file. + /// + /// New frame data. + public void AddLowLevelFrame(IntPtr frameData) { + lock (this) { + // write to stream + if (Avi32.AVIStreamWrite(streamCompressed, position, 1, frameData, stride * height, 0, IntPtr.Zero, IntPtr.Zero) != 0) { + throw new ApplicationException("Failed adding frame"); + } + + position++; + } + } + } + + /// + /// Windows API functions and structures. + /// + /// + /// The class provides Video for Windows and some other Avi32 functions and structurs. + /// + internal static class Avi32 { + /// + /// Copy a block of memory. + /// + /// + /// Destination pointer. + /// Source pointer. + /// Memory block's length to copy. + /// + /// Return's the value of dst - pointer to destination. + /// + [DllImport("ntdll.dll")] + public static extern int memcpy(int dst, int src, int count); + + + // --- Video for Windows Functions + + /// + /// Initialize the AVIFile library. + /// + /// + [DllImport("avifil32.dll")] + public static extern void AVIFileInit(); + + /// + /// Exit the AVIFile library. + /// + [DllImport("avifil32.dll")] + public static extern void AVIFileExit(); + + /// + /// Open an AVI file. + /// + /// + /// Opened AVI file interface. + /// AVI file name. + /// Opening mode (see ). + /// Handler to use (null to use default). + /// + /// Returns zero on success or error code otherwise. + /// + [DllImport("avifil32.dll", CharSet = CharSet.Unicode)] + public static extern int AVIFileOpen(out IntPtr aviHandler, String fileName, OpenFileMode mode, IntPtr handler); + + /// + /// Release an open AVI stream. + /// + /// + /// Open AVI file interface. + /// + /// Returns the reference count of the file. + /// + [DllImport("avifil32.dll")] + public static extern int AVIFileRelease(IntPtr aviHandler); + + /// + /// Get stream interface that is associated with a specified AVI file + /// + /// + /// Handler to an open AVI file. + /// Stream interface. + /// Stream type to open. + /// Count of the stream type. Identifies which occurrence of the specified stream type to access. + /// + /// + /// + [DllImport("avifil32.dll")] + public static extern int AVIFileGetStream(IntPtr aviHandler, out IntPtr streamHandler, int streamType, int streamNumner); + + /// + /// Create a new stream in an existing file and creates an interface to the new stream. + /// + /// + /// Handler to an open AVI file. + /// Stream interface. + /// Pointer to a structure containing information about the new stream. + /// + /// Returns zero if successful or an error otherwise. + /// + [DllImport("avifil32.dll")] + public static extern int AVIFileCreateStream(IntPtr aviHandler, out IntPtr streamHandler, ref AVISTREAMINFO streamInfo); + + /// + /// Release an open AVI stream. + /// + /// + /// Handle to an open stream. + /// + /// Returns the current reference count of the stream. + /// + [DllImport("avifil32.dll")] + public static extern int AVIStreamRelease(IntPtr streamHandler); + + /// + /// Set the format of a stream at the specified position. + /// + /// + /// Handle to an open stream. + /// Position in the stream to receive the format. + /// Pointer to a structure containing the new format. + /// Size, in bytes, of the block of memory referenced by format. + /// + /// Returns zero if successful or an error otherwise. + /// + [DllImport("avifil32.dll")] + public static extern int AVIStreamSetFormat(IntPtr streamHandler, int position, ref BitmapInfoHeader format, int formatSize); + + /// + /// Get the starting sample number for the stream. + /// + /// + /// Handle to an open stream. + /// + /// Returns the number if successful or – 1 otherwise. + /// + [DllImport("avifil32.dll")] + public static extern int AVIStreamStart(IntPtr streamHandler); + + /// + /// Get the length of the stream. + /// + /// Handle to an open stream. + /// Returns the stream's length, in samples, if successful or -1 otherwise. + [DllImport("avifil32.dll")] + public static extern int AVIStreamLength(IntPtr streamHandler); + + /// + /// Obtain stream header information. + /// + /// + /// Handle to an open stream. + /// Pointer to a structure to contain the stream information. + /// Size, in bytes, of the structure used for streamInfo. + /// + /// Returns zero if successful or an error otherwise. + /// + [DllImport("avifil32.dll", CharSet = CharSet.Unicode)] + public static extern int AVIStreamInfo(IntPtr streamHandler, ref AVISTREAMINFO streamInfo, int infoSize); + + /// + /// Prepare to decompress video frames from the specified video stream + /// + /// Pointer to the video stream used as the video source. + /// Pointer to a structure that defines the desired video format. Specify NULL to use a default format. + /// Returns an object that can be used with the function. + [DllImport("avifil32.dll")] + public static extern IntPtr AVIStreamGetFrameOpen(IntPtr streamHandler, ref BitmapInfoHeader wantedFormat); + + /// + /// Prepare to decompress video frames from the specified video stream. + /// + /// Pointer to the video stream used as the video source. + /// Pointer to a structure that defines the desired video format. Specify NULL to use a default format. + /// Returns a GetFrame object that can be used with the function. + [DllImport("avifil32.dll")] + public static extern IntPtr AVIStreamGetFrameOpen(IntPtr streamHandler, int wantedFormat); + + /// + /// Releases resources used to decompress video frames. + /// + /// Handle returned from the function. + /// Returns zero if successful or an error otherwise. + [DllImport("avifil32.dll")] + public static extern int AVIStreamGetFrameClose(IntPtr getFrameObject); + + /// + /// Return the address of a decompressed video frame. + /// + /// Pointer to a GetFrame object. + /// Position, in samples, within the stream of the desired frame. + /// Returns a pointer to the frame data if successful or NULL otherwise. + [DllImport("avifil32.dll")] + public static extern IntPtr AVIStreamGetFrame(IntPtr getFrameObject, int position); + + /// + /// Write data to a stream. + /// + /// Handle to an open stream. + /// First sample to write. + /// Number of samples to write. + /// Pointer to a buffer containing the data to write. + /// Size of the buffer referenced by buffer. + /// Flag associated with this data. + /// Pointer to a buffer that receives the number of samples written. This can be set to NULL. + /// Pointer to a buffer that receives the number of bytes written. This can be set to NULL. + /// + /// Returns zero if successful or an error otherwise. + /// + [DllImport("avifil32.dll")] + public static extern int AVIStreamWrite(IntPtr streamHandler, int start, int samples, IntPtr buffer, int bufferSize, int flags, IntPtr samplesWritten, IntPtr bytesWritten); + + /// + /// Retrieve the save options for a file and returns them in a buffer. + /// + /// Handle to the parent window for the Compression Options dialog box. + /// Flags for displaying the Compression Options dialog box. + /// Number of streams that have their options set by the dialog box. + /// Pointer to an array of stream interface pointers. + /// Pointer to an array of pointers to AVICOMPRESSOPTIONS structures. + /// Returns TRUE if the user pressed OK, FALSE for CANCEL, or an error otherwise. + [DllImport("avifil32.dll")] + public static extern int AVISaveOptions(IntPtr window, int flags, int streams, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] streamInterfaces, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] options); + + /// + /// Free the resources allocated by the AVISaveOptions function. + /// + /// Count of the AVICOMPRESSOPTIONS structures referenced in options. + /// Pointer to an array of pointers to AVICOMPRESSOPTIONS structures. + /// Returns 0. + [DllImport("avifil32.dll")] + public static extern int AVISaveOptionsFree(int streams, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] options); + + /// + /// Create a compressed stream from an uncompressed stream and a + /// compression filter, and returns the address of a pointer to + /// the compressed stream. + /// + /// Pointer to a buffer that receives the compressed stream pointer. + /// Pointer to the stream to be compressed. + /// Pointer to a structure that identifies the type of compression to use and the options to apply. + /// Pointer to a class identifier used to create the stream. + /// Returns 0 if successful or an error otherwise. + [DllImport("avifil32.dll")] + public static extern int AVIMakeCompressedStream(out IntPtr compressedStream, IntPtr sourceStream, ref AVICOMPRESSOPTIONS options, IntPtr clsidHandler); + + // --- structures + + /// + /// Structure, which contains information for a single stream . + /// + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)] + public struct AVISTREAMINFO + { + /// + /// Four-character code indicating the stream type. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int type; + + /// + /// Four-character code of the compressor handler that will compress this video stream when it is saved. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int handler; + + /// + /// Applicable flags for the stream. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int flags; + + /// + /// Capability flags; currently unused. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int ñapabilities; + + /// + /// Priority of the stream. + /// + /// + [MarshalAs(UnmanagedType.I2)] + public short priority; + + /// + /// Language of the stream. + /// + /// + [MarshalAs(UnmanagedType.I2)] + public short language; + + /// + /// Time scale applicable for the stream. + /// + /// + /// Dividing rate by scale gives the playback rate in number of samples per second. + /// + [MarshalAs(UnmanagedType.I4)] + public int scale; + + /// + /// Rate in an integer format. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int rate; + + /// + /// Sample number of the first frame of the AVI file. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int start; + + /// + /// Length of this stream. + /// + /// + /// The units are defined by rate and scale. + /// + [MarshalAs(UnmanagedType.I4)] + public int length; + + /// + /// Audio skew. This member specifies how much to skew the audio data ahead of the video frames in interleaved files. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int initialFrames; + + /// + /// Recommended buffer size, in bytes, for the stream. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int suggestedBufferSize; + + /// + /// Quality indicator of the video data in the stream. + /// + /// + /// Quality is represented as a number between 0 and 10,000. + /// + [MarshalAs(UnmanagedType.I4)] + public int quality; + + /// + /// Size, in bytes, of a single data sample. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int sampleSize; + + /// + /// Dimensions of the video destination rectangle. + /// + /// + [MarshalAs(UnmanagedType.Struct, SizeConst = 16)] + public RECT rectFrame; + + /// + /// Number of times the stream has been edited. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int editCount; + + /// + /// Number of times the stream format has changed. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int formatChangeCount; + + /// + /// Description of the stream. + /// + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string name; + } + + /// + /// Structure, which contains information about a stream and how it is compressed and saved. + /// + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AVICOMPRESSOPTIONS + { + /// + /// Four-character code indicating the stream type. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int type; + + /// + /// Four-character code for the compressor handler that will compress this video stream when it is saved. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int handler; + + /// + /// Maximum period between video key frames. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int keyFrameEvery; + + /// + /// Quality value passed to a video compressor. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int quality; + + /// + /// Video compressor data rate. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int bytesPerSecond; + + /// + /// Flags used for compression. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int flags; + + /// + /// Pointer to a structure defining the data format. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int format; + + /// + /// Size, in bytes, of the data referenced by format. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int formatSize; + + /// + /// Video-compressor-specific data; used internally. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int parameters; + + /// + /// Size, in bytes, of the data referenced by parameters. + /// + [MarshalAs(UnmanagedType.I4)] + public int parametersSize; + + /// + /// Interleave factor for interspersing stream data with data from the first stream. + /// + /// + [MarshalAs(UnmanagedType.I4)] + public int interleaveEvery; + } + + // --- enumerations + + /// + /// File access modes. + /// + /// + [Flags] + public enum OpenFileMode { + Read = 0x00000000, + Write = 0x00000001, + ReadWrite = 0x00000002, + ShareCompat = 0x00000000, + ShareExclusive = 0x00000010, + ShareDenyWrite = 0x00000020, + ShareDenyRead = 0x00000030, + ShareDenyNone = 0x00000040, + Parse = 0x00000100, + Delete = 0x00000200, + Verify = 0x00000400, + Cancel = 0x00000800, + Create = 0x00001000, + Prompt = 0x00002000, + Exist = 0x00004000, + Reopen = 0x00008000 + } + + /// + /// .NET replacement of mmioFOURCC macros. Converts four characters to code. + /// + /// + /// Four characters string. + /// + /// Returns the code created from provided characters. + /// + public static int mmioFOURCC(string str) { + return ( ((int)(byte)(str[0])) | + ((int)(byte)(str[1]) << 8) | + ((int)(byte)(str[2]) << 16) | + ((int)(byte)(str[3]) << 24)); + } + + /// + /// Inverse to . Converts code to fout characters string. + /// + /// + /// Code to convert. + /// + /// Returns four characters string. + /// + public static string decode_mmioFOURCC(int code) { + char[] chs = new char[4]; + + for (int i = 0; i < 4; i++) { + chs[i] = (char)(byte)((code >> (i << 3)) & 0xFF); + if (!char.IsLetterOrDigit(chs[i])) { + chs[i] = ' '; + } + } + return new string(chs); + } + + + /// + /// Version of for one stream only. + /// + /// + /// Stream to configure. + /// Stream options. + /// + /// Returns TRUE if the user pressed OK, FALSE for CANCEL, or an error otherwise. + /// + public static int AVISaveOptions(IntPtr stream, ref AVICOMPRESSOPTIONS options) { + IntPtr[] streams = new IntPtr[1]; + IntPtr[] infPtrs = new IntPtr[1]; + IntPtr mem; + int ret; + + // alloc unmanaged memory + mem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(AVICOMPRESSOPTIONS))); + + // copy from managed structure to unmanaged memory + Marshal.StructureToPtr(options, mem, false); + + streams[0] = stream; + infPtrs[0] = mem; + + // show dialog with a list of available compresors and configuration + ret = AVISaveOptions(IntPtr.Zero, 0, 1, streams, infPtrs); + + // copy from unmanaged memory to managed structure + options = (AVICOMPRESSOPTIONS)Marshal.PtrToStructure(mem, typeof(AVICOMPRESSOPTIONS)); + + // free AVI compression options + AVISaveOptionsFree(1, infPtrs); + + // clear it, because the information already freed by AVISaveOptionsFree + options.format = 0; + options.parameters = 0; + + // free unmanaged memory + Marshal.FreeHGlobal(mem); + + return ret; + } + } +} diff --git a/Greenshot/Helpers/CaptureHelper.cs b/Greenshot/Helpers/CaptureHelper.cs new file mode 100644 index 000000000..a286c077a --- /dev/null +++ b/Greenshot/Helpers/CaptureHelper.cs @@ -0,0 +1,725 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2011 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Printing; +using System.IO; +using System.Windows.Forms; + +using Greenshot.Configuration; +using Greenshot.Drawing; +using Greenshot.Helpers; +using Greenshot.Forms; +using Greenshot.Plugin; +using GreenshotPlugin.UnmanagedHelpers; +using GreenshotPlugin.Core; +using IniFile; + +namespace Greenshot.Helpers { + /// + /// CaptureHelper contains all the capture logic + /// + public class CaptureHelper { + private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(CaptureHelper)); + private static CoreConfiguration conf = IniConfig.GetIniSection(); + private static ScreenCaptureHelper screenCapture = null; + private List windows = new List(); + private WindowDetails selectedCaptureWindow = null; + private Rectangle captureRect = Rectangle.Empty; + private bool captureMouseCursor = false; + private ICapture capture = null; + private ILanguage lang = Language.GetInstance(); + private CaptureMode captureMode; + + public static void CaptureClipboard() { + new CaptureHelper(CaptureMode.Clipboard).MakeCapture(); + } + public static void CaptureRegion(bool captureMouse) { + new CaptureHelper(CaptureMode.Region, captureMouse).MakeCapture(); + } + public static void CaptureRegion(bool captureMouse, IDestination destination) { + CaptureHelper captureHelper = new CaptureHelper(CaptureMode.Region, captureMouse, destination); + captureHelper.MakeCapture(); + } + public static void CaptureFullscreen(bool captureMouse) { + new CaptureHelper(CaptureMode.FullScreen, captureMouse).MakeCapture(); + } + public static void CaptureLastRegion(bool captureMouse) { + new CaptureHelper(CaptureMode.LastRegion, captureMouse).MakeCapture(); + } + public static void CaptureIE(bool captureMouse) { + new CaptureHelper(CaptureMode.IE, captureMouse).MakeCapture(); + } + public static void CaptureWindow(bool captureMouse) { + new CaptureHelper(CaptureMode.ActiveWindow, captureMouse).MakeCapture(); + } + public static void CaptureWindow(WindowDetails windowToCapture) { + CaptureHelper captureHelper = new CaptureHelper(CaptureMode.ActiveWindow); + captureHelper.SelectedCaptureWindow = windowToCapture; + captureHelper.MakeCapture(); + } + public static void CaptureWindowInteractive(bool captureMouse) { + new CaptureHelper(CaptureMode.Window, captureMouse).MakeCapture(); + } + public static void CaptureFile(string filename) { + new CaptureHelper(CaptureMode.File).MakeCapture(filename); + } + + public static void ImportCapture(ICapture captureToImport) { + CaptureHelper captureHelper = new CaptureHelper(CaptureMode.File); + captureHelper.capture = captureToImport; + captureHelper.HandleCapture(); + } + + public CaptureHelper(CaptureMode captureMode) { + this.captureMode = captureMode; + capture = new Capture(); + } + + public CaptureHelper(CaptureMode captureMode, bool captureMouseCursor) : this(captureMode) { + this.captureMouseCursor = captureMouseCursor; + } + + public CaptureHelper(CaptureMode captureMode, bool captureMouseCursor, IDestination destination) : this(captureMode, captureMouseCursor) { + capture.CaptureDetails.AddDestination(destination); + } + + public WindowDetails SelectedCaptureWindow { + get { + return selectedCaptureWindow; + } + set { + selectedCaptureWindow = value; + } + } + + private void DoCaptureFeedback() { + if(conf.PlayCameraSound) { + SoundHelper.Play(); + } + } + + /// + /// Make Capture with file name + /// + /// filename + private void MakeCapture(string filename) { + capture.CaptureDetails.Filename = filename; + MakeCapture(); + } + + /// + /// Make Capture with specified destinations + /// + private void MakeCapture() { + + // Experimental code + if (screenCapture != null) { + screenCapture.Stop(); + screenCapture = null; + return; + } + + LOG.Debug(String.Format("Capturing with mode {0} and using Cursor {1})", captureMode, captureMouseCursor)); + capture.CaptureDetails.CaptureMode = captureMode; + + // Add destinations if no-one passed a handler + if (capture.CaptureDetails.CaptureDestinations == null || capture.CaptureDetails.CaptureDestinations.Count == 0) { + AddConfiguredDestination(); + } + PrepareForCaptureWithFeedback(); + + // Workaround for proble with DPI retrieval, the FromHwnd activates the window... + WindowDetails previouslyActiveWindow = WindowDetails.GetActiveWindow(); + // Workaround for changed DPI settings in Windows 7 + using (Graphics graphics = Graphics.FromHwnd(MainForm.instance.Handle)) { + capture.CaptureDetails.DpiX = graphics.DpiX; + capture.CaptureDetails.DpiY = graphics.DpiY; + } + if (previouslyActiveWindow != null) { + // Set previouslyActiveWindow as foreground window + previouslyActiveWindow.ToForeground(); + } + + // Delay for the Context menu + if (conf.CaptureDelay > 0) { + System.Threading.Thread.Sleep(conf.CaptureDelay); + } else { + conf.CaptureDelay = 0; + } + + // Allways capture Mousecursor, only show when needed + capture = WindowCapture.CaptureCursor(capture); + capture.CursorVisible = false; + // Check if needed + if (captureMouseCursor && captureMode != CaptureMode.Clipboard && captureMode != CaptureMode.File) { + capture.CursorVisible = conf.CaptureMousepointer; + } + + switch(captureMode) { + case CaptureMode.Window: + capture = WindowCapture.CaptureScreen(capture); + capture.CaptureDetails.AddMetaData("source", "Screen"); + CaptureWithFeedback(); + break; + case CaptureMode.ActiveWindow: + if (CaptureActiveWindow()) { + // Capture worked, offset mouse according to screen bounds and capture location + capture.MoveMouseLocation(capture.ScreenBounds.Location.X-capture.Location.X, capture.ScreenBounds.Location.Y-capture.Location.Y); + capture.MoveElements(capture.ScreenBounds.Location.X-capture.Location.X, capture.ScreenBounds.Location.Y-capture.Location.Y); + capture.CaptureDetails.AddMetaData("source", "Window"); + } else { + captureMode = CaptureMode.FullScreen; + capture = WindowCapture.CaptureScreen(capture); + capture.CaptureDetails.AddMetaData("source", "Screen"); + capture.CaptureDetails.Title = "Screen"; + } + HandleCapture(); + break; + case CaptureMode.IE: + if (IECaptureHelper.CaptureIE(capture) != null) { + capture.CaptureDetails.AddMetaData("source", "Internet Explorer"); + HandleCapture(); + } + break; + case CaptureMode.FullScreen: + capture = WindowCapture.CaptureScreen(capture); + HandleCapture(); + break; + case CaptureMode.Clipboard: + Image clipboardImage = null; + string text = "Clipboard"; + if (ClipboardHelper.ContainsImage()) { + clipboardImage = ClipboardHelper.GetImage(); + } + if (clipboardImage != null) { + if (capture != null) { + capture.Image = clipboardImage; + } else { + capture = new Capture(clipboardImage); + } + string title = ClipboardHelper.GetText(); + if (title == null || title.Trim().Length == 0) { + title = "Clipboard"; + } + capture.CaptureDetails.Title = title; + capture.CaptureDetails.AddMetaData("source", "Clipboard"); + // Force Editor, keep picker + if (capture.CaptureDetails.HasDestination(Destinations.PickerDestination.DESIGNATION)) { + capture.CaptureDetails.ClearDestinations(); + capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(Destinations.EditorDestination.DESIGNATION)); + capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(Destinations.PickerDestination.DESIGNATION)); + } else { + capture.CaptureDetails.ClearDestinations(); + capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(Destinations.EditorDestination.DESIGNATION)); + } + HandleCapture(); + } else { + MessageBox.Show("Couldn't create bitmap from : " + text); + } + break; + case CaptureMode.File: + Bitmap fileBitmap = null; + string filename = capture.CaptureDetails.Filename; + if (!string.IsNullOrEmpty(filename)) { + try { + fileBitmap = ImageHelper.LoadBitmap(filename); + } catch (Exception e) { + LOG.Error(e.Message, e); + MessageBox.Show(lang.GetFormattedString(LangKey.error_openfile, filename)); + } + } + if (fileBitmap != null) { + capture.CaptureDetails.Title = Path.GetFileNameWithoutExtension(filename); + capture.CaptureDetails.AddMetaData("file", filename); + capture.CaptureDetails.AddMetaData("source", "file"); + if (capture != null) { + capture.Image = fileBitmap; + } else { + capture = new Capture(fileBitmap); + } + // Force Editor, keep picker, this is currently the only usefull destination + if (capture.CaptureDetails.HasDestination("Picker")) { + capture.CaptureDetails.ClearDestinations(); + capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(Destinations.EditorDestination.DESIGNATION)); + capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(Destinations.PickerDestination.DESIGNATION)); + } else { + capture.CaptureDetails.ClearDestinations(); + capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(Destinations.EditorDestination.DESIGNATION)); + } + HandleCapture(); + } + break; + case CaptureMode.LastRegion: + if (!RuntimeConfig.LastCapturedRegion.IsEmpty) { + capture = WindowCapture.CaptureScreen(capture); + capture.Crop(RuntimeConfig.LastCapturedRegion); + capture.CaptureDetails.AddMetaData("source", "screen"); + HandleCapture(); + } + break; + case CaptureMode.Region: + capture = WindowCapture.CaptureScreen(capture); + capture.CaptureDetails.AddMetaData("source", "screen"); + CaptureWithFeedback(); + break; + case CaptureMode.Video: + capture = WindowCapture.CaptureScreen(capture); + // Set the capturemode to be window + captureMode = CaptureMode.Window; + capture.CaptureDetails.AddMetaData("source", "Video"); + CaptureWithFeedback(); + break; + default: + LOG.Warn("Unknown capture mode: " + captureMode); + break; + } + } + + /// + /// Pre-Initialization for CaptureWithFeedback, this will get all the windows before we change anything + /// + private void PrepareForCaptureWithFeedback() { + windows = new List(); + // Start Enumeration of "active" windows + foreach (WindowDetails window in WindowDetails.GetAllWindows()) { + // Window should be visible and not ourselves + if (!window.Visible) { + continue; + } + + // Skip empty + Rectangle windowRectangle = window.WindowRectangle; + Size windowSize = windowRectangle.Size; + if (windowSize.Width == 0 || windowSize.Height == 0) { + continue; + } + + // Make sure the details are retrieved once + window.FreezeDetails(); + + // Force children retrieval, sometimes windows close on losing focus and this is solved by caching + int goLevelDeep = 3; + if (conf.WindowCaptureAllChildLocations) { + goLevelDeep = 20; + } + window.GetChildren(goLevelDeep); + windows.Add(window); + + // Get window rectangle as capture Element + CaptureElement windowCaptureElement = new CaptureElement(windowRectangle); + capture.Elements.Add(windowCaptureElement); + + if (!window.HasParent) { + // Get window client rectangle as capture Element, place all the other "children" in there + Rectangle clientRectangle = window.ClientRectangle; + CaptureElement windowClientCaptureElement = new CaptureElement(clientRectangle); + windowCaptureElement.Children.Add(windowClientCaptureElement); + AddCaptureElementsForWindow(windowClientCaptureElement, window, goLevelDeep); + } else { + AddCaptureElementsForWindow(windowCaptureElement, window, goLevelDeep); + } + + } + windows = WindowDetails.SortByZOrder(IntPtr.Zero, windows); + } + + private void AddCaptureElementsForWindow(ICaptureElement parentElement, WindowDetails parentWindow, int level) { + foreach(WindowDetails childWindow in parentWindow.Children) { + // Make sure the details are retrieved once + childWindow.FreezeDetails(); + Rectangle childRectangle = childWindow.WindowRectangle; + Size s1 = childRectangle.Size; + childRectangle.Intersect(parentElement.Bounds); + if (childRectangle.Width > 0 && childRectangle.Height > 0) { + CaptureElement childCaptureElement = new CaptureElement(childRectangle); + parentElement.Children.Add(childCaptureElement); + if (level > 0) { + AddCaptureElementsForWindow(childCaptureElement, childWindow, level -1); + } + } + } + } + + private void AddConfiguredDestination() { + foreach(string destinationDesignation in conf.OutputDestinations) { + IDestination destination = DestinationHelper.GetDestination(destinationDesignation); + if (destination != null) { + capture.CaptureDetails.AddDestination(destination); + } + } + } + + private void HandleCapture() { + // Flag to see if the image was "exported" so the FileEditor doesn't + // ask to save the file as long as nothing is done. + bool outputMade = false; + + // Make sure the user sees that the capture is made + if (capture.CaptureDetails.CaptureMode != CaptureMode.File && capture.CaptureDetails.CaptureMode != CaptureMode.Clipboard) { + DoCaptureFeedback(); + } else { + // If File || Clipboard + // Maybe not "made" but the original is still there... somehow + outputMade = true; + } + + LOG.Debug("A capture of: " + capture.CaptureDetails.Title); + + // check if someone has passed a destination + if (capture.CaptureDetails.CaptureDestinations == null || capture.CaptureDetails.CaptureDestinations.Count == 0) { + AddConfiguredDestination(); + } + + // Create Surface with capture, this way elements can be added automatically (like the mouse cursor) + Surface surface = new Surface(capture); + + // Register notify events if this is wanted + if (conf.ShowTrayNotification) { + surface.SurfaceMessage += delegate(object source, SurfaceMessageEventArgs eventArgs) { + switch (eventArgs.MessageType) { + case SurfaceMessageTyp.Error: + MainForm.instance.notifyIcon.ShowBalloonTip(10000, "Greenshot", eventArgs.Message, ToolTipIcon.Error); + break; + case SurfaceMessageTyp.Info: + MainForm.instance.notifyIcon.ShowBalloonTip(10000, "Greenshot", eventArgs.Message, ToolTipIcon.Info); + break; + case SurfaceMessageTyp.FileSaved: + EventHandler balloonTipClickedHandler = null; + EventHandler balloonTipClosedHandler = null; + balloonTipClosedHandler = delegate(object sender, EventArgs e) { + LOG.DebugFormat("Deregistering the BalloonTipClosed"); + MainForm.instance.notifyIcon.BalloonTipClicked -= balloonTipClickedHandler; + MainForm.instance.notifyIcon.BalloonTipClosed -= balloonTipClosedHandler; + }; + + balloonTipClickedHandler = delegate(object sender, EventArgs e) { + if (surface.LastSaveFullPath != null) { + ProcessStartInfo psi = new ProcessStartInfo("explorer"); + psi.Arguments = Path.GetDirectoryName(eventArgs.Surface.LastSaveFullPath); + psi.UseShellExecute = false; + Process p = new Process(); + p.StartInfo = psi; + p.Start(); + } + LOG.DebugFormat("Deregistering the BalloonTipClicked"); + MainForm.instance.notifyIcon.BalloonTipClicked -= balloonTipClickedHandler; + MainForm.instance.notifyIcon.BalloonTipClosed -= balloonTipClosedHandler; + }; + MainForm.instance.notifyIcon.BalloonTipClicked += balloonTipClickedHandler; + MainForm.instance.notifyIcon.BalloonTipClosed += balloonTipClosedHandler; + MainForm.instance.notifyIcon.ShowBalloonTip(10000, "Greenshot", eventArgs.Message, ToolTipIcon.Info); + break; + } + }; + + } + // Let the processors do their job + foreach(IProcessor processor in ProcessorHelper.GetAllProcessors()) { + if (processor.isActive) { + LOG.InfoFormat("Calling processor {0}", processor.Description); + processor.ProcessCapture(surface, capture.CaptureDetails); + } + } + + // As the surfaces copies the reference to the image, make sure the image is not being disposed (a trick to save memory) + capture.Image = null; + + // Get CaptureDetails as we need it even after the capture is disposed + ICaptureDetails captureDetails = capture.CaptureDetails; + bool canDisposeSurface = true; + + if (captureDetails.HasDestination(Destinations.PickerDestination.DESIGNATION)) { + DestinationHelper.ExportCapture(Destinations.PickerDestination.DESIGNATION, surface, captureDetails); + captureDetails.CaptureDestinations.Clear(); + canDisposeSurface = false; + } + + // Disable capturing + captureMode = CaptureMode.None; + // Dispose the capture, we don't need it anymore (the surface copied all information and we got the title (if any)). + capture.Dispose(); + capture = null; + + int destinationCount = captureDetails.CaptureDestinations.Count; + if (destinationCount > 0) { + // Flag to detect if we need to create a temp file for the email + // or use the file that was written + foreach(IDestination destination in captureDetails.CaptureDestinations) { + if ("Picker".Equals(destination.Designation)) { + continue; + } + LOG.InfoFormat("Calling destination {0}", destination.Description); + + bool destinationOk = destination.ExportCapture(surface, captureDetails); + if (Destinations.EditorDestination.DESIGNATION.Equals(destination.Designation) && destinationOk) { + canDisposeSurface = false; + } + outputMade = outputMade || destinationOk; + } + } + if (canDisposeSurface) { + surface.Dispose(); + } + } + + private bool CaptureActiveWindow() { + bool presupplied = false; + LOG.Debug("CaptureActiveWindow"); + if (selectedCaptureWindow != null) { + LOG.Debug("Using supplied window"); + presupplied = true; + } else { + selectedCaptureWindow = WindowDetails.GetActiveWindow(); + if (selectedCaptureWindow != null) { + LOG.DebugFormat("Capturing window: {0} with {1}", selectedCaptureWindow.Text, selectedCaptureWindow.WindowRectangle); + } + } + if (selectedCaptureWindow == null || (!presupplied && selectedCaptureWindow.Iconic)) { + LOG.Warn("No window to capture!"); + // Nothing to capture, code up in the stack will capture the full screen + return false; + } + if (selectedCaptureWindow != null && selectedCaptureWindow.Iconic) { + // Restore the window making sure it's visible! + // This is done mainly for a screen capture, but some applications like Excel and TOAD have weird behaviour! + selectedCaptureWindow.Restore(); + } + selectedCaptureWindow.ToForeground(); + selectedCaptureWindow = SelectCaptureWindow(selectedCaptureWindow); + if (selectedCaptureWindow == null) { + LOG.Warn("No window to capture, after SelectCaptureWindow!"); + // Nothing to capture, code up in the stack will capture the full screen + return false; + } + // Fix for Bug #3430560 + RuntimeConfig.LastCapturedRegion = selectedCaptureWindow.WindowRectangle; + bool returnValue = CaptureWindow(selectedCaptureWindow, capture, conf.WindowCaptureMode) != null; + return returnValue; + } + + /// + /// Select the window to capture, this has logic which takes care of certain special applications + /// like TOAD or Excel + /// + /// WindowDetails with the target Window + /// WindowDetails with the target Window OR a replacement + public static WindowDetails SelectCaptureWindow(WindowDetails windowToCapture) { + Rectangle windowRectangle = windowToCapture.WindowRectangle; + if (windowToCapture.Iconic || windowRectangle.Width == 0 || windowRectangle.Height == 0) { + LOG.WarnFormat("Window {0} has nothing to capture, using workaround to find other window of same process.", windowToCapture.Text); + // Trying workaround, the size 0 arrises with e.g. Toad.exe, has a different Window when minimized + WindowDetails linkedWindow = WindowDetails.GetLinkedWindow(windowToCapture); + if (linkedWindow != null) { + windowRectangle = linkedWindow.WindowRectangle; + windowToCapture = linkedWindow; + } else { + return null; + } + } + return windowToCapture; + } + + /// + /// Check if Process uses PresentationFramework.dll -> meaning it uses WPF + /// + /// + /// true if the process uses WPF + private static bool isWPF(Process process) { + if (process != null) { + foreach(ProcessModule module in process.Modules) { + if (module.ModuleName.StartsWith("PresentationFramework")) { + LOG.InfoFormat("Found that Process {0} uses {1}, assuming it's using WPF", process.ProcessName, module.FileName); + return true; + } + } + } + return false; + } + + /// + /// Capture the supplied Window + /// + /// Window to capture + /// The capture to store the details + /// What WindowCaptureMode to use + /// + public static ICapture CaptureWindow(WindowDetails windowToCapture, ICapture captureForWindow, WindowCaptureMode windowCaptureMode) { + if (captureForWindow == null) { + captureForWindow = new Capture(); + } + Rectangle windowRectangle = windowToCapture.WindowRectangle; + if (windowToCapture.Iconic) { + // Restore the window making sure it's visible! + windowToCapture.Restore(); + } + + // When Vista & DWM (Aero) enabled + bool dwmEnabled = DWM.isDWMEnabled(); + // get process name to be able to exclude certain processes from certain capture modes + Process process = windowToCapture.Process; + bool isAutoMode = windowCaptureMode == WindowCaptureMode.Auto; + // For WindowCaptureMode.Auto we check: + // 1) Is window IE, use IE Capture + // 2) Is Windows >= Vista & DWM enabled: use DWM + // 3) Otherwise use GDI (Screen might be also okay but might lose content) + if (isAutoMode) { + if (conf.IECapture && windowToCapture.ClassName == "IEFrame") { + try { + ICapture ieCapture = IECaptureHelper.CaptureIE(captureForWindow); + if (ieCapture != null) { + return ieCapture; + } + } catch (Exception ex) { + LOG.WarnFormat("Problem capturing IE, skipping to normal capture. Exception message was: {0}", ex.Message); + } + } + + // Take default screen + windowCaptureMode = WindowCaptureMode.Screen; + + // Change to GDI, if allowed + if (conf.isGDIAllowed(process)) { + if (!dwmEnabled && isWPF(process)) { + // do not use GDI, as DWM is not enabled and the application uses PresentationFramework.dll -> isWPF + LOG.InfoFormat("Not using GDI for windows of process {0}, as the process uses WPF", process.ProcessName); + } else { + windowCaptureMode = WindowCaptureMode.GDI; + } + } + + // Change to DWM, if enabled and allowed + if (dwmEnabled) { + if (conf.isDWMAllowed(process)) { + windowCaptureMode = WindowCaptureMode.Aero; + } + } + } else if (windowCaptureMode == WindowCaptureMode.Aero || windowCaptureMode == WindowCaptureMode.AeroTransparent) { + if (!dwmEnabled || !conf.isDWMAllowed(process)) { + // Take default screen + windowCaptureMode = WindowCaptureMode.Screen; + // Change to GDI, if allowed + if (conf.isGDIAllowed(process)) { + windowCaptureMode = WindowCaptureMode.GDI; + } + } + } else if (windowCaptureMode == WindowCaptureMode.GDI && !conf.isGDIAllowed(process)) { + // GDI not allowed, take screen + windowCaptureMode = WindowCaptureMode.Screen; + } + + LOG.InfoFormat("Capturing window with mode {0}", windowCaptureMode); + bool captureTaken = false; + // Try to capture + while (!captureTaken) { + if (windowCaptureMode == WindowCaptureMode.GDI) { + ICapture tmpCapture = null; + if (conf.isGDIAllowed(process)) { + tmpCapture = windowToCapture.CaptureWindow(captureForWindow); + } + if (tmpCapture != null) { + captureForWindow = tmpCapture; + captureTaken = true; + } else { + // A problem, try Screen + windowCaptureMode = WindowCaptureMode.Screen; + } + } else if (windowCaptureMode == WindowCaptureMode.Aero || windowCaptureMode == WindowCaptureMode.AeroTransparent) { + ICapture tmpCapture = null; + if (conf.isDWMAllowed(process)) { + tmpCapture = windowToCapture.CaptureDWMWindow(captureForWindow, windowCaptureMode, isAutoMode); + } + if (tmpCapture != null) { + captureForWindow = tmpCapture; + captureTaken = true; + } else { + // A problem, try GDI + windowCaptureMode = WindowCaptureMode.GDI; + } + } else { + // Screen capture + windowRectangle.Intersect(captureForWindow.ScreenBounds); + try { + captureForWindow = WindowCapture.CaptureRectangle(captureForWindow, windowRectangle); + captureTaken = true; + } catch (Exception e) { + LOG.Error("Problem capturing", e); + return null; + } + } + } + + if (captureForWindow != null && windowToCapture != null) { + captureForWindow.CaptureDetails.Title = windowToCapture.Text; + ((Bitmap)captureForWindow.Image).SetResolution(captureForWindow.CaptureDetails.DpiX, captureForWindow.CaptureDetails.DpiY); + } + + return captureForWindow; + } + + #region capture with feedback + private void CaptureWithFeedback() { + using (CaptureForm captureForm = new CaptureForm(capture, windows)) { + DialogResult result = captureForm.ShowDialog(); + if (result == DialogResult.OK) { + selectedCaptureWindow = captureForm.SelectedCaptureWindow; + captureRect = captureForm.CaptureRectangle; + // Get title + if (selectedCaptureWindow != null) { + capture.CaptureDetails.Title = selectedCaptureWindow.Text; + } + + // Experimental code + if (capture.CaptureDetails.CaptureMode == CaptureMode.Video) { + if (captureForm.UsedCaptureMode == CaptureMode.Window) { + screenCapture = new ScreenCaptureHelper(selectedCaptureWindow); + } else if (captureForm.UsedCaptureMode == CaptureMode.Region) { + screenCapture = new ScreenCaptureHelper(captureRect); + } + if (screenCapture != null) { + screenCapture.RecordMouse = capture.CursorVisible; + if (screenCapture.Start(15)) { + return; + } + // User clicked cancel or a problem occured + screenCapture.Stop(); + screenCapture = null; + return; + } + } + + if (captureRect.Height > 0 && captureRect.Width > 0) { + // Take the captureRect, this already is specified as bitmap coordinates + capture.Crop(captureRect); + // save for re-capturing later and show recapture context menu option + RuntimeConfig.LastCapturedRegion = captureRect; + HandleCapture(); + } + } + } + } + + #endregion + } +} diff --git a/Greenshot/Helpers/DestinationHelper.cs b/Greenshot/Helpers/DestinationHelper.cs new file mode 100644 index 000000000..c98d45f8a --- /dev/null +++ b/Greenshot/Helpers/DestinationHelper.cs @@ -0,0 +1,124 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2011 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.Collections.Generic; + +using Greenshot.Plugin; +using GreenshotPlugin.Core; + +namespace Greenshot.Helpers { + /// + /// Description of DestinationHelper. + /// + public static class DestinationHelper { + private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(DestinationHelper)); + private static Dictionary RegisteredDestinations = new Dictionary(); + + /// Initialize the destinations + static DestinationHelper() { + foreach(Type destinationType in InterfaceUtils.GetSubclassesOf(typeof(IDestination),true)) { + // Only take our own + if (!"Greenshot.Destinations".Equals(destinationType.Namespace)) { + continue; + } + if (!destinationType.IsAbstract) { + IDestination destination; + try { + destination = (IDestination)Activator.CreateInstance(destinationType); + } catch (Exception e) { + LOG.ErrorFormat("Can't create instance of {0}", destinationType); + LOG.Error(e); + continue; + } + if (destination.isActive) { + LOG.DebugFormat("Found destination {0} with designation {1}", destinationType.Name, destination.Designation); + RegisterDestination(destination); + } else { + LOG.DebugFormat("Ignoring destination {0} with designation {1}", destinationType.Name, destination.Designation); + } + } + } + } + + /// + /// Register your destination here, if it doesn't come from a plugin and needs to be available + /// + /// + public static void RegisterDestination(IDestination destination) { + // don't test the key, an exception should happen wenn it's not unique + RegisteredDestinations.Add(destination.Designation, destination); + } + + /// + /// Get a list of all destinations, registered or supplied by a plugin + /// + /// + public static List GetAllDestinations() { + List destinations = new List(); + destinations.AddRange(RegisteredDestinations.Values); + foreach(IGreenshotPlugin plugin in PluginHelper.instance.Plugins.Values) { + destinations.AddRange(plugin.Destinations()); + } + destinations.Sort(); + return destinations; + } + + /// + /// Get a destination by a designation + /// + /// Designation of the destination + /// IDestination or null + public static IDestination GetDestination(string designation) { + if (designation == null) { + return null; + } + if (RegisteredDestinations.ContainsKey(designation)) { + return RegisteredDestinations[designation]; + } + foreach(IGreenshotPlugin plugin in PluginHelper.instance.Plugins.Values) { + foreach(IDestination destination in plugin.Destinations()) { + if (designation.Equals(destination.Designation)) { + return destination; + } + } + } + return null; + } + + /// + /// A simple helper method which will call ExportCapture for the destination with the specified designation + /// + /// + /// + /// + public static void ExportCapture(string designation, ISurface surface, ICaptureDetails captureDetails) { + if (RegisteredDestinations.ContainsKey(designation)) { + IDestination destination = RegisteredDestinations[designation]; + if (destination.isActive) { + if (destination.ExportCapture(surface, captureDetails)) { + // Export worked, set the modified flag + surface.Modified = false; + } + } + } + } + } +} diff --git a/Greenshot/Helpers/GeometryHelper.cs b/Greenshot/Helpers/GeometryHelper.cs new file mode 100644 index 000000000..21414a559 --- /dev/null +++ b/Greenshot/Helpers/GeometryHelper.cs @@ -0,0 +1,63 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2011 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; + +namespace Greenshot.Helpers { + /// + /// Description of GeometryHelper. + /// + public static class GeometryHelper { + /// + /// Finds the distance between two points on a 2D surface. + /// + /// The point on the x-axis of the first point + /// The point on the x-axis of the second point + /// The point on the y-axis of the first point + /// The point on the y-axis of the second point + /// + public static int Distance2D(int x1, int y1, int x2, int y2) { + //Our end result + int result = 0; + //Take x2-x1, then square it + double part1 = Math.Pow((x2 - x1), 2); + //Take y2-y1, then square it + double part2 = Math.Pow((y2 - y1), 2); + //Add both of the parts together + double underRadical = part1 + part2; + //Get the square root of the parts + result = (int)Math.Sqrt(underRadical); + //Return our result + return result; + } + + /// + /// Calculates the angle of a line defined by two points on a 2D surface. + /// + /// The point on the x-axis of the first point + /// The point on the x-axis of the second point + /// The point on the y-axis of the first point + /// The point on the y-axis of the second point + /// + public static double Angle2D(int x1, int y1, int x2, int y2) { + return Math.Atan2(y2 - y1, x2 - x1) * 180 / Math.PI; + } + } +} diff --git a/Greenshot/Helpers/ProcessorHelper.cs b/Greenshot/Helpers/ProcessorHelper.cs new file mode 100644 index 000000000..12da10ab6 --- /dev/null +++ b/Greenshot/Helpers/ProcessorHelper.cs @@ -0,0 +1,121 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2011 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.Collections.Generic; + +using Greenshot.Plugin; +using GreenshotPlugin.Core; + +namespace Greenshot.Helpers { + /// + /// Description of ProcessorHelper. + /// + public static class ProcessorHelper { + private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ProcessorHelper)); + private static Dictionary RegisteredProcessors = new Dictionary(); + + /// Initialize the Processors + static ProcessorHelper() { + foreach(Type ProcessorType in InterfaceUtils.GetSubclassesOf(typeof(IProcessor),true)) { + // Only take our own + if (!"Greenshot.Processors".Equals(ProcessorType.Namespace)) { + continue; + } + if (!ProcessorType.IsAbstract) { + IProcessor Processor; + try { + Processor = (IProcessor)Activator.CreateInstance(ProcessorType); + } catch (Exception e) { + LOG.ErrorFormat("Can't create instance of {0}", ProcessorType); + LOG.Error(e); + continue; + } + if (Processor.isActive) { + LOG.DebugFormat("Found Processor {0} with designation {1}", ProcessorType.Name, Processor.Designation); + RegisterProcessor(Processor); + } else { + LOG.DebugFormat("Ignoring Processor {0} with designation {1}", ProcessorType.Name, Processor.Designation); + } + } + } + } + + /// + /// Register your Processor here, if it doesn't come from a plugin and needs to be available + /// + /// + public static void RegisterProcessor(IProcessor Processor) { + // don't test the key, an exception should happen wenn it's not unique + RegisteredProcessors.Add(Processor.Designation, Processor); + } + + /// + /// Get a list of all Processors, registered or supplied by a plugin + /// + /// + public static List GetAllProcessors() { + List Processors = new List(); + Processors.AddRange(RegisteredProcessors.Values); + foreach(IGreenshotPlugin plugin in PluginHelper.instance.Plugins.Values) { + Processors.AddRange(plugin.Processors()); + } + Processors.Sort(); + return Processors; + } + + /// + /// Get a Processor by a designation + /// + /// Designation of the Processor + /// IProcessor or null + public static IProcessor GetProcessor(string designation) { + if (designation == null) { + return null; + } + if (RegisteredProcessors.ContainsKey(designation)) { + return RegisteredProcessors[designation]; + } + foreach(IGreenshotPlugin plugin in PluginHelper.instance.Plugins.Values) { + foreach(IProcessor Processor in plugin.Processors()) { + if (designation.Equals(Processor.Designation)) { + return Processor; + } + } + } + return null; + } + + /// + /// A simple helper method which will call ProcessCapture for the Processor with the specified designation + /// + /// + /// + /// + public static void ProcessCapture(string designation, ISurface surface, ICaptureDetails captureDetails) { + if (RegisteredProcessors.ContainsKey(designation)) { + IProcessor Processor = RegisteredProcessors[designation]; + if (Processor.isActive) { + Processor.ProcessCapture(surface, captureDetails); + } + } + } + } +} diff --git a/Greenshot/Helpers/ScreenCaptureHelper.cs b/Greenshot/Helpers/ScreenCaptureHelper.cs new file mode 100644 index 000000000..325dc2ab8 --- /dev/null +++ b/Greenshot/Helpers/ScreenCaptureHelper.cs @@ -0,0 +1,262 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2011 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Threading; +using System.Windows.Forms; + +using Greenshot; +using Greenshot.Configuration; +using Greenshot.Plugin; +using GreenshotPlugin.UnmanagedHelpers; +using GreenshotPlugin.Core; +using IniFile; + +namespace Greenshot.Helpers { + /// + /// Description of ScreenCaptureHelper. + /// + public class ScreenCaptureHelper { + private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ScreenCaptureHelper)); + private static CoreConfiguration conf = IniConfig.GetIniSection(); + private const int MAX_FRAMES = 500; + private IntPtr hWndDesktop = IntPtr.Zero; + private IntPtr hDCDesktop = IntPtr.Zero; + private IntPtr hDCDest = IntPtr.Zero; + private IntPtr hDIBSection = IntPtr.Zero; + private IntPtr hOldObject = IntPtr.Zero; + private int framesPerSecond; + private Thread backgroundTask; + private bool stop = false; + private AVIWriter aviWriter; + private WindowDetails recordingWindow; + private Rectangle recordingRectangle; + public bool RecordMouse = false; + private Size recordingSize; + private IntPtr bits0 = IntPtr.Zero; //pointer to the raw bits that make up the bitmap. + private Bitmap GDIBitmap; + private string filename = null; + + public ScreenCaptureHelper(Rectangle recordingRectangle) { + this.recordingRectangle = recordingRectangle; + } + public ScreenCaptureHelper(WindowDetails recordingWindow) { + this.recordingWindow = recordingWindow; + } + + /// + /// Helper method to create an exception that might explain what is wrong while capturing + /// + /// string with current method + /// Rectangle of what we want to capture + /// + private static Exception CreateCaptureException(string method, Size size) { + Exception exceptionToThrow = User32.CreateWin32Exception(method); + if (size != Size.Empty) { + exceptionToThrow.Data.Add("Height", size.Height); + exceptionToThrow.Data.Add("Width", size.Width); + } + return exceptionToThrow; + } + + public bool Start(int framesPerSecond) { + if (recordingWindow != null) { + string windowTitle = Regex.Replace(recordingWindow.Text, @"[^\x20\d\w]", ""); + if (string.IsNullOrEmpty(windowTitle)) { + windowTitle = "greenshot-recording"; + } + filename = Path.Combine(conf.OutputFilePath, windowTitle + ".avi"); + + } else { + filename = Path.Combine(conf.OutputFilePath, "greenshot-recording.avi"); + } + if (File.Exists(filename)) { + try { + File.Delete(filename); + } catch {} + } + LOG.InfoFormat("Capturing to {0}", filename); + + if (recordingWindow != null) { + LOG.InfoFormat("Starting recording Window '{0}', {1}", recordingWindow.Text, recordingWindow.WindowRectangle); + recordingSize = recordingWindow.WindowRectangle.Size; + } else { + LOG.InfoFormat("Starting recording rectangle {0}", recordingRectangle); + recordingSize = recordingRectangle.Size; + } + if (recordingSize.Width % 8 > 0) { + LOG.InfoFormat("Correcting width to be factor 8, {0} => {1}", recordingSize.Width, recordingSize.Width + (8-(recordingSize.Width % 8))); + recordingSize = new Size(recordingSize.Width + (8-(recordingSize.Width % 8)), recordingSize.Height); + } + if (recordingSize.Height % 8 > 0) { + LOG.InfoFormat("Correcting Height to be factor 8, {0} => {1}", recordingSize.Height, recordingSize.Height + (8-(recordingSize.Height % 8))); + recordingSize = new Size(recordingSize.Width, recordingSize.Height + (8-(recordingSize.Height % 8))); + } + this.framesPerSecond = framesPerSecond; + // "P/Invoke" Solution for capturing the screen + hWndDesktop = User32.GetDesktopWindow(); + // get te hDC of the target window + hDCDesktop = User32.GetWindowDC(hWndDesktop); + // Make sure the last error is set to 0 + Win32.SetLastError(0); + + // create a device context we can copy to + hDCDest = GDI32.CreateCompatibleDC(hDCDesktop); + // Check if the device context is there, if not throw an error with as much info as possible! + if (hDCDest == IntPtr.Zero) { + // Get Exception before the error is lost + Exception exceptionToThrow = CreateCaptureException("CreateCompatibleDC", recordingSize); + // Cleanup + User32.ReleaseDC(hWndDesktop, hDCDesktop); + // throw exception + throw exceptionToThrow; + } + + // Create BitmapInfoHeader for CreateDIBSection + BitmapInfoHeader bitmapInfoHeader = new BitmapInfoHeader(recordingSize.Width, recordingSize.Height, 32); + + // 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 + hDIBSection = GDI32.CreateDIBSection(hDCDesktop, ref bitmapInfoHeader, BitmapInfoHeader.DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0); + + if (hDIBSection == IntPtr.Zero) { + // Get Exception before the error is lost + Exception exceptionToThrow = CreateCaptureException("CreateDIBSection", recordingSize); + exceptionToThrow.Data.Add("hdcDest", hDCDest.ToInt32()); + exceptionToThrow.Data.Add("hdcSrc", hDCDesktop.ToInt32()); + + // clean up + GDI32.DeleteDC(hDCDest); + User32.ReleaseDC(hWndDesktop, hDCDesktop); + + // Throw so people can report the problem + throw exceptionToThrow; + } + // Create a GDI Bitmap so we can use GDI and GDI+ operations on the same memory + GDIBitmap = new Bitmap(recordingSize.Width, recordingSize.Height, 32, PixelFormat.Format32bppArgb, bits0); + // select the bitmap object and store the old handle + hOldObject = GDI32.SelectObject(hDCDest, hDIBSection); + stop = false; + + aviWriter = new AVIWriter(); + // Comment the following 2 lines to make the user select it's own codec + //aviWriter.Codec = "msvc"; + //aviWriter.Quality = 99; + + aviWriter.FrameRate = framesPerSecond; + if (aviWriter.Open(filename, recordingSize.Width, recordingSize.Height)) { + // Start update check in the background + backgroundTask = new Thread (new ThreadStart(CaptureFrame)); + backgroundTask.IsBackground = true; + backgroundTask.Start(); + return true; + } else { + // Cancel + aviWriter.Dispose(); + aviWriter = null; + } + return false; + } + + private void CaptureFrame() { + int MSBETWEENCAPTURES = 1000/framesPerSecond; + int msToNextCapture = MSBETWEENCAPTURES; + while (!stop) { + DateTime nextCapture = DateTime.Now.AddMilliseconds(msToNextCapture); + Point captureLocation; + if (recordingWindow != null) { + recordingWindow.Reset(); + captureLocation = recordingWindow.Location; + } else { + captureLocation = new Point(recordingRectangle.X, recordingRectangle.Y); + } + // "Capture" + GDI32.BitBlt(hDCDest, 0, 0, recordingSize.Width, recordingSize.Height, hDCDesktop, captureLocation.X, captureLocation.Y, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt); + + // Mouse + if (RecordMouse) { + CursorInfo cursorInfo = new CursorInfo(); + cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); + Point mouseLocation = Cursor.Position; + mouseLocation.Offset(-captureLocation.X, -captureLocation.Y); + if (User32.GetCursorInfo(out cursorInfo)) { + User32.DrawIcon(hDCDest, mouseLocation.X, mouseLocation.Y, cursorInfo.hCursor); + } + } + // add to avi + try { + aviWriter.AddLowLevelFrame(bits0); + } catch (Exception) { + LOG.Error("Error adding frame to avi, stopping capturing."); + break; + } + int sleeptime = (int)(nextCapture.Subtract(DateTime.Now).Ticks / TimeSpan.TicksPerMillisecond); + if (sleeptime > 0) { + Thread.Sleep(sleeptime); + msToNextCapture = MSBETWEENCAPTURES; + } else { + // Compensating + msToNextCapture = Math.Max(0, MSBETWEENCAPTURES - sleeptime); + } + } + Cleanup(); + } + + public void Stop() { + stop = true; + if (backgroundTask != null) { + backgroundTask.Join(); + } + Cleanup(); + } + /// + /// Free resources + /// + private void Cleanup() { + if (hOldObject != IntPtr.Zero && hDCDest != IntPtr.Zero) { + // restore selection (old handle) + GDI32.SelectObject(hDCDest, hOldObject); + GDI32.DeleteDC(hDCDest); + } + if (hDCDesktop != IntPtr.Zero) { + User32.ReleaseDC(hWndDesktop, hDCDesktop); + } + if (hDIBSection != IntPtr.Zero) { + // free up the Bitmap object + GDI32.DeleteObject(hDIBSection); + } + if (aviWriter != null) { + aviWriter.Dispose(); + aviWriter = null; + MessageBox.Show("Recording written to " + filename); + } + } + } +} diff --git a/Greenshot/Helpers/WindowWrapper.cs b/Greenshot/Helpers/WindowWrapper.cs new file mode 100644 index 000000000..356b336a2 --- /dev/null +++ b/Greenshot/Helpers/WindowWrapper.cs @@ -0,0 +1,35 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2011 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; + +namespace Greenshot.Helpers { + public class WindowWrapper : System.Windows.Forms.IWin32Window { + public WindowWrapper(IntPtr handle) { + _hwnd = handle; + } + + public IntPtr Handle { + get { return _hwnd; } + } + + private IntPtr _hwnd; + } +}