/*
* Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2010 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.Windows.Forms;
using Greenshot;
using Greenshot.Capturing;
using Greenshot.Configuration;
using Greenshot.Plugin;
using Greenshot.UnmanagedHelpers;
namespace Greenshot.Helpers {
///
/// This Class is used to pass details about the capture around.
/// The time the Capture was taken and the Title of the window (or a region of) that is captured
///
public class CaptureDetails : ICaptureDetails {
private string title;
public string Title {
get {return title;}
set {title = value;}
}
private string filename;
public string Filename {
get {return filename;}
set {filename = value;}
}
private DateTime dateTime;
public DateTime DateTime {
get {return dateTime;}
set {dateTime = value;}
}
private CaptureMode captureMode;
public CaptureMode CaptureMode {
get {return captureMode;}
set {captureMode = value;}
}
private List captureDestinations = new List();
public List CaptureDestinations {
get {return captureDestinations;}
set {captureDestinations = value;}
}
private CaptureHandler captureHandler;
public CaptureHandler CaptureHandler {
get {return captureHandler;}
set {captureHandler = value;}
}
public void ClearDestinations() {
captureDestinations.Clear();
}
public void RemoveDestination(CaptureDestination captureDestination) {
if (!captureDestinations.Contains(captureDestination)) {
captureDestinations.Remove(captureDestination);
}
}
public void AddDestination(CaptureDestination captureDestination) {
if (!captureDestinations.Contains(captureDestination)) {
captureDestinations.Add(captureDestination);
}
}
public CaptureDetails() {
dateTime = DateTime.Now;
}
}
///
/// This class is used to pass an instance of the "Capture" around
/// Having the Bitmap, eventually the Windows Title and cursor all together.
///
public class Capture : IDisposable, ICapture {
private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(Capture));
private Rectangle screenBounds;
///
/// Get/Set the Screenbounds
///
public Rectangle ScreenBounds {
get {return screenBounds;}
set {screenBounds = value;}
}
private Image image;
///
/// Get/Set the Image
///
public Image Image {
get {return image;}
set {
if (image != null) {
image.Dispose();
}
image = value;
if (value != null) {
if (value.PixelFormat.Equals(PixelFormat.Format8bppIndexed) || value.PixelFormat.Equals(PixelFormat.Format1bppIndexed) || value.PixelFormat.Equals(PixelFormat.Format4bppIndexed)) {
LOG.Debug("Converting Bitmap to PixelFormat.Format32bppArgb as we don't support: " + value.PixelFormat);
try {
// Default Bitmap PixelFormat is Format32bppArgb
this.image = new Bitmap(value);
} finally {
// Always dispose, even when a exception occured
value.Dispose();
}
}
LOG.Debug("Image is set with the following specifications: " + image.Width + "," + image.Height + " - " + image.PixelFormat);
} else {
LOG.Debug("Image is removed.");
}
}
}
private Icon cursor;
///
/// Get/Set the image for the Cursor
///
public Icon Cursor {
get {return cursor;}
set {
if (cursor != null) {
cursor.Dispose();
}
cursor = (Icon)value.Clone();
}
}
private bool cursorVisible = false;
///
/// Set if the cursor is visible
///
public bool CursorVisible {
get {return cursorVisible;}
set {cursorVisible = value;}
}
private Point cursorLocation = Point.Empty;
///
/// Get/Set the CursorLocation
///
public Point CursorLocation {
get {return cursorLocation;}
set {cursorLocation = value;}
}
private Point location = Point.Empty;
///
/// Get/set the Location
///
public Point Location {
get {return location;}
set {location = value;}
}
private CaptureDetails captureDetails;
///
/// Get/set the CaptureDetails
///
public ICaptureDetails CaptureDetails {
get {return captureDetails;}
set {captureDetails = (CaptureDetails)value;}
}
///
/// Default Constructor
///
public Capture() {
screenBounds = WindowCapture.GetScreenBounds();
captureDetails = new CaptureDetails();
}
///
/// Constructor with Image
/// Note: the supplied bitmap can be disposed immediately or when constructor is called.
///
/// Image
public Capture(Image newImage) : this() {
this.Image = newImage;
}
///
/// Destructor
///
~Capture() {
Dispose(false);
}
///
/// The public accessible Dispose
/// Will call the GarbageCollector to SuppressFinalize, preventing being cleaned twice
///
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// This Dispose is called from the Dispose and the Destructor.
/// When disposing==true all non-managed resources should be freed too!
///
///
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (image != null) {
image.Dispose();
}
if (cursor != null) {
cursor.Dispose();
}
}
image = null;
cursor = null;
}
///
/// Crops the capture to the specified rectangle (with Bitmap coordinates!)
///
/// Rectangle with bitmap coordinates
public void Crop(Rectangle cropRectangle) {
LOG.Debug("Cropping to: " + cropRectangle.ToString());
ImageHelper.Crop(ref image, ref cropRectangle);
location = cropRectangle.Location;
// Change mouse location according to the cropRegtangle (including screenbounds) offset
MoveMouseLocation(-cropRectangle.Location.X, -cropRectangle.Location.Y);
}
///
/// Apply a translate to the mouse location.
/// e.g. needed for crop
///
/// x coordinates to move the mouse
/// y coordinates to move the mouse
public void MoveMouseLocation(int x, int y) {
cursorLocation.Offset(x, y);
}
///
/// Crops the capture to the specified rectangle (with screen coordinates!)
///
/// Rectangle with screen coordinates
public void CropWithScreenCoordinates(Rectangle cropRectangle) {
LOG.Debug("Cropping to (Screen coordinates): " + cropRectangle.ToString());
// Convert the cropRectangle from Screen coordinates to Bitmap coordinates
cropRectangle.Offset(-screenBounds.Location.X, -screenBounds.Location.Y);
Crop(cropRectangle);
}
}
///
/// The Window Capture code
///
public class WindowCapture {
private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(WindowCapture));
private WindowCapture() {
}
///
/// Get the bounds of all screens combined.
///
/// A Rectangle of the bounds of the entire display area.
public static Rectangle GetScreenBounds() {
int left = 0, top = 0, bottom = 0, right = 0;
foreach (Screen screen in Screen.AllScreens) {
left = Math.Min(left, screen.Bounds.X);
top = Math.Min(top, screen.Bounds.Y);
int screenAbsRight = screen.Bounds.X + screen.Bounds.Width;
int screenAbsBottom = screen.Bounds.Y + screen.Bounds.Height;
right = Math.Max(right, screenAbsRight);
bottom = Math.Max(bottom, screenAbsBottom);
}
return new Rectangle(left, top, (right + Math.Abs(left)), (bottom + Math.Abs(top)));
}
///
/// This method will capture the current Cursor by using User32 Code
///
/// A Capture Object with the Mouse Cursor information in it.
public static ICapture CaptureCursor(ICapture capture) {
LOG.Debug("Capturing the mouse cursor.");
if (capture == null) {
capture = new Capture();
}
int x,y;
IntPtr hicon;
User32.CursorInfo cursorInfo = new User32.CursorInfo();
User32.IconInfo iconInfo;
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
if (User32.GetCursorInfo(out cursorInfo)) {
if (cursorInfo.flags == User32.CURSOR_SHOWING) {
hicon = User32.CopyIcon(cursorInfo.hCursor);
if (User32.GetIconInfo(hicon, out iconInfo)) {
// Allign cursor location to Bitmap coordinates (instead of Screen coordinates)
x = cursorInfo.ptScreenPos.x - iconInfo.xHotspot - capture.ScreenBounds.X;
y = cursorInfo.ptScreenPos.y - iconInfo.yHotspot - capture.ScreenBounds.Y;
// Set the location
capture.CursorLocation = new Point(x, y);
using (Icon icon = Icon.FromHandle(hicon)) {
capture.Cursor = icon;
}
if (iconInfo.hbmMask != IntPtr.Zero) {
GDI32.DeleteObject(iconInfo.hbmMask);
}
if (iconInfo.hbmColor != IntPtr.Zero) {
GDI32.DeleteObject(iconInfo.hbmColor);
}
}
User32.DestroyIcon(hicon);
}
}
return capture;
}
///
/// This method will call the CaptureRectangle with the screenbounds, therefor Capturing the whole screen.
///
/// A Capture Object with the Screen as an Image
public static ICapture CaptureScreen(ICapture capture) {
if (capture == null) {
capture = new Capture();
}
return CaptureRectangle(capture, capture.ScreenBounds);
}
///
/// Helper method to create an exception that might explain what is wrong while capturing
///
/// string with current method
/// ICapture
/// Rectangle of what we want to capture
///
private static Exception CreateCaptureException(string method, ICapture capture, Rectangle captureBounds) {
Exception exceptionToThrow = User32.CreateWin32Exception(method);
if (captureBounds != Rectangle.Empty) {
exceptionToThrow.Data.Add("Height", captureBounds.Height);
exceptionToThrow.Data.Add("Width", captureBounds.Width);
}
if (capture != null) {
exceptionToThrow.Data.Add("Capturing mode", capture.CaptureDetails.CaptureMode);
}
Rectangle screenbounds = GetScreenBounds();
exceptionToThrow.Data.Add("Screenbounds", String.Format("{0},{1},{2},{3}", screenbounds.X, screenbounds.Y, screenbounds.Width, screenbounds.Height));
return exceptionToThrow;
}
///
/// This method will use User32 code to capture the specified captureBounds from the screen
///
/// A Capture Object with a part of the Screen as an Image
public static ICapture CaptureRectangle(ICapture capture, Rectangle captureBounds) {
LOG.Debug("CaptureRectangle Called!");
if (capture == null) {
capture = new Capture();
}
// .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;
// "P/Invoke" Solution for capturing the screen
IntPtr hWndDesktop = User32.GetDesktopWindow();
// get te hDC of the target window
IntPtr hDCDesktop = User32.GetWindowDC(hWndDesktop);
// Make sure the last error is set to 0
Win32.SetLastError(0);
// create a device context we can copy to
IntPtr 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", capture, captureBounds);
// Cleanup
User32.ReleaseDC(hWndDesktop, hDCDesktop);
// 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.
IntPtr hDIBSection = GDI32.CreateDIBSection(hDCDesktop, ref bmi, BitmapInfoHeader.DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0);
if (hDIBSection == IntPtr.Zero) {
// Get Exception before the error is lost
Exception exceptionToThrow = CreateCaptureException("CreateDIBSection", capture, captureBounds);
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;
} else {
// select the bitmap object and store the old handle
IntPtr hOldObject = GDI32.SelectObject(hDCDest, hDIBSection);
// bitblt over (make copy)
GDI32.BitBlt(hDCDest, 0, 0, captureBounds.Width, captureBounds.Height, hDCDesktop, captureBounds.X, captureBounds.Y, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
// restore selection (old handle)
GDI32.SelectObject(hDCDest, hOldObject);
// clean up
GDI32.DeleteDC(hDCDest);
User32.ReleaseDC(hWndDesktop, hDCDesktop);
// 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 {
// assign image to Capture, the image will be disposed there..
capture.Image = Bitmap.FromHbitmap(hDIBSection);
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;
}
capture.Location = captureBounds.Location;
// free up the Bitmap object
GDI32.DeleteObject(hDIBSection);
}
return capture;
}
}
}