From 4a64ca98e57474093457947584ad36f940371cf8 Mon Sep 17 00:00:00 2001 From: Michael Brabec Date: Sat, 24 Apr 2021 01:51:02 +0200 Subject: [PATCH] Version 1.6.0: Inactive screenshot capture, white background optimization --- AeroShot.csproj | 40 ++++++++++- EmptyForm.Designer.cs | 50 ++++++++++++++ EmptyForm.cs | 18 +++++ EmptyForm.resx | 120 ++++++++++++++++++++++++++++++++ Hotkeys.cs | 1 + Main.Designer.cs | 1 + Main.cs | 1 + Program.cs | 21 +++--- README.md | 14 ++++ Screenshot.cs | 155 +++++++++++++++++++++++++++++------------- Settings.cs | 1 + Tray.cs | 1 + WindowsAPI.cs | 1 + app.config | 3 + 14 files changed, 371 insertions(+), 56 deletions(-) create mode 100644 EmptyForm.Designer.cs create mode 100644 EmptyForm.cs create mode 100644 EmptyForm.resx create mode 100644 app.config diff --git a/AeroShot.csproj b/AeroShot.csproj index 303c879..714d631 100644 --- a/AeroShot.csproj +++ b/AeroShot.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,6 +10,21 @@ v3.0 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true true @@ -21,6 +36,7 @@ 4 AnyCPU true + false pdbonly @@ -31,6 +47,7 @@ 4 AnyCPU true + false AeroShot @@ -41,10 +58,18 @@ + + + + Form + + + EmptyForm.cs + Form @@ -64,11 +89,24 @@ + + EmptyForm.cs + Main.cs + + + + + + False + .NET Framework 3.5 SP1 + false + + diff --git a/EmptyForm.Designer.cs b/EmptyForm.Designer.cs new file mode 100644 index 0000000..2bdc29b --- /dev/null +++ b/EmptyForm.Designer.cs @@ -0,0 +1,50 @@ +namespace AeroShot +{ + partial class EmptyForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // EmptyForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(5, 5); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.Location = new System.Drawing.Point(5, 5); + this.Name = "EmptyForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.Text = "Form1"; + this.TopMost = true; + this.ResumeLayout(false); + + } + + #endregion + } +} \ No newline at end of file diff --git a/EmptyForm.cs b/EmptyForm.cs new file mode 100644 index 0000000..d309969 --- /dev/null +++ b/EmptyForm.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace AeroShot +{ + public partial class EmptyForm : Form + { + public EmptyForm() + { + InitializeComponent(); + } + } +} diff --git a/EmptyForm.resx b/EmptyForm.resx new file mode 100644 index 0000000..7080a7d --- /dev/null +++ b/EmptyForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Hotkeys.cs b/Hotkeys.cs index ba8fa89..5643579 100644 --- a/Hotkeys.cs +++ b/Hotkeys.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 Copyright (C) 2012 Caleb Joseph diff --git a/Main.Designer.cs b/Main.Designer.cs index afa4b9b..af748ad 100644 --- a/Main.Designer.cs +++ b/Main.Designer.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 Copyright (C) 2012 Caleb Joseph diff --git a/Main.cs b/Main.cs index 0d93a6b..63369d0 100644 --- a/Main.cs +++ b/Main.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 Copyright (C) 2012 Caleb Joseph diff --git a/Program.cs b/Program.cs index 0ebfbec..a7e2c1b 100644 --- a/Program.cs +++ b/Program.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 Copyright (C) 2012 Caleb Joseph @@ -20,12 +21,12 @@ You should have received a copy of the GNU General Public License using System.Runtime.InteropServices; using System.Windows.Forms; -[assembly: AssemblyTitle("AeroShot Mini")] -[assembly: AssemblyProduct("AeroShot Mini")] +[assembly: AssemblyTitle("AeroShotCRE")] +[assembly: AssemblyProduct("AeroShotCRE")] [assembly: AssemblyDescription("Screenshot capture utility for Windows Aero")] -[assembly: AssemblyCopyright("\u00a9 2015 toe_head2001")] -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] +[assembly: AssemblyCopyright("\u00a9 2021 Cvolton")] +[assembly: AssemblyVersion("1.6.0.0")] +[assembly: AssemblyFileVersion("1.6.0.0")] [assembly: ComVisible(false)] namespace AeroShot @@ -41,7 +42,7 @@ private static void Main() return; } - bool isFirstInstance; + bool isFirstInstance; // set if truly first instance: var mutex = new System.Threading.Mutex(true, "AeroShot", out isFirstInstance); @@ -49,12 +50,14 @@ private static void Main() if (!isFirstInstance) { return; - } + } + //temporary workaround - we need to lose focus for inactive screenshot captures to work + MessageBox.Show("Press OK to start the application", Application.ProductName); - Application.EnableVisualStyles(); + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new SysTray()); - GC.KeepAlive(mutex); + //GC.KeepAlive(mutex); } } } \ No newline at end of file diff --git a/README.md b/README.md index 29bbd4a..05daf95 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ +AeroShotCRE is a fork of AeroShot aiming to add features useful to creators of Windows Crazy Error videos. This program saves 4 screenshots with the following file name suffixes: +* b1 - Optimized for dark/black backgrounds, active titlebar +* b2 - Optimized for dark/black backgrounds, inactive titlebar +* w1 - Optimized for light/white backgrounds, active titlebar +* w2 - Optimized for light/white backgrounds, inactive titlebar + +## Changelog + +### 1.6.0 +* Automatic inactive screenshot capture +* Enabled the algorithm for white backgrounds for better colored title bars +* Temporarily disabled checkerboard and solid backgrounds + +# AeroShot AeroShot is a free (GPLv3) and open source screenshot utility designed for capturing individual windows. It captures screenshots **with full transparency**, such as seen in the Windows Aero visual style. This allows for a clean and professional screenshot, useful for showcasing an application. The entire program is written in C#, heavily utilizing the Windows API. AeroShot was originally developed by Caleb Joseph in 2011, and was inspired by the proprietary [Window Clippings](http://www.windowclippings.com/) program. diff --git a/Screenshot.cs b/Screenshot.cs index 38d6c26..40544b5 100644 --- a/Screenshot.cs +++ b/Screenshot.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 Copyright (C) 2012 Caleb Joseph @@ -16,6 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; +using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; @@ -164,7 +166,7 @@ internal static void CaptureWindow(ref ScreenshotTask data) foreach (char inv in Path.GetInvalidFileNameChars()) name = name.Replace(inv.ToString(), string.Empty); - Bitmap s = CaptureCompositeScreenshot(ref data); + Bitmap[] s = CaptureCompositeScreenshot(ref data); if (AeroColorToggled) { @@ -200,19 +202,19 @@ internal static void CaptureWindow(ref ScreenshotTask data) if (data.ClipboardNotDisk && data.Background != ScreenshotTask.BackgroundType.Transparent) // Screenshot is already opaque, don't need to modify it - Clipboard.SetImage(s); + Clipboard.SetImage(s[0]); else if (data.ClipboardNotDisk) { - var whiteS = new Bitmap(s.Width, s.Height, PixelFormat.Format24bppRgb); + var whiteS = new Bitmap(s[0].Width, s[0].Height, PixelFormat.Format24bppRgb); using (Graphics graphics = Graphics.FromImage(whiteS)) { graphics.Clear(Color.White); - graphics.DrawImage(s, 0, 0, s.Width, s.Height); + graphics.DrawImage(s[0], 0, 0, s[0].Width, s[0].Height); } using (var stream = new MemoryStream()) { // Save screenshot in clipboard as PNG which some applications support (eg. Microsoft Office) - s.Save(stream, ImageFormat.Png); + s[0].Save(stream, ImageFormat.Png); var pngClipboardData = new DataObject("PNG", stream); // Add fallback for applications that don't support PNG from clipboard (eg. Photoshop or Paint) @@ -227,20 +229,30 @@ internal static void CaptureWindow(ref ScreenshotTask data) name = name.Trim(); if (name == string.Empty) name = "AeroShot"; - string path = Path.Combine(data.DiskSaveDirectory, name + ".png"); + string pathActive = Path.Combine(data.DiskSaveDirectory, name + "_b1.png"); + string pathInactive = Path.Combine(data.DiskSaveDirectory, name + "_b2.png"); + string pathWhiteActive = Path.Combine(data.DiskSaveDirectory, name + "_w1.png"); + string pathWhiteInactive = Path.Combine(data.DiskSaveDirectory, name + "_w2.png"); - if (File.Exists(path)) + if (File.Exists(pathActive) || File.Exists(pathInactive)) { for (int i = 1; i < 9999; i++) { - path = Path.Combine(data.DiskSaveDirectory, name + " " + i + ".png"); - if (!File.Exists(path)) + pathActive = Path.Combine(data.DiskSaveDirectory, name + " " + i + "_b1.png"); + pathInactive = Path.Combine(data.DiskSaveDirectory, name + " " + i + "_b2.png"); + pathWhiteActive = Path.Combine(data.DiskSaveDirectory, name + " " + i + "_b1.png"); + pathWhiteInactive = Path.Combine(data.DiskSaveDirectory, name + " " + i + "_b2.png"); + if (!File.Exists(pathActive)) break; } } - s.Save(path, ImageFormat.Png); - } - s.Dispose(); + s[0].Save(pathActive, ImageFormat.Png); + s[1].Save(pathInactive, ImageFormat.Png); + s[2].Save(pathWhiteActive, ImageFormat.Png); + s[3].Save(pathWhiteInactive, ImageFormat.Png); + } + s[0].Dispose(); + s[1].Dispose(); } if (data.DoResize) @@ -317,7 +329,7 @@ private static void SmartResizeWindow(ref ScreenshotTask data, out WindowsRect o WindowsApi.GetWindowRect(data.WindowHandle, ref r); oldWindowSize = r; - Bitmap f = CaptureCompositeScreenshot(ref data); + Bitmap f = CaptureCompositeScreenshot(ref data)[0]; if (f != null) { WindowsApi.SetWindowPos(data.WindowHandle, (IntPtr)0, r.Left, r.Top, data.ResizeX - (f.Width - (r.Right - r.Left)), data.ResizeY - (f.Height - (r.Bottom - r.Top)), SWP_SHOWWINDOW); @@ -329,9 +341,9 @@ private static void SmartResizeWindow(ref ScreenshotTask data, out WindowsRect o } } - private static unsafe Bitmap CaptureCompositeScreenshot(ref ScreenshotTask data) + private static unsafe Bitmap[] CaptureCompositeScreenshot(ref ScreenshotTask data) { - Color tmpColor = data.BackgroundColor; + Color tmpColor = data.BackgroundColor; if (data.Background == ScreenshotTask.BackgroundType.Transparent || data.Background == ScreenshotTask.BackgroundType.Checkerboard) tmpColor = Color.White; @@ -377,11 +389,13 @@ private static unsafe Bitmap CaptureCompositeScreenshot(ref ScreenshotTask data) WindowsApi.SetWindowPos(backdrop.Handle, data.WindowHandle, rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top, SWP_NOACTIVATE); backdrop.Opacity = 1; Application.DoEvents(); + SendKeys.SendWait("%{F16}"); - // Capture screenshot with white background - Bitmap whiteShot = CaptureScreenRegion(new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top)); + // Capture screenshot with white background + Bitmap whiteShot = CaptureScreenRegion(new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top)); - if (data.Background == ScreenshotTask.BackgroundType.SolidColor) + //TODO: Solid Color Background + /*if (data.Background == ScreenshotTask.BackgroundType.SolidColor) { backdrop.Dispose(); if (data.CaptureMouse) @@ -389,25 +403,56 @@ private static unsafe Bitmap CaptureCompositeScreenshot(ref ScreenshotTask data) Bitmap final = CropEmptyEdges(whiteShot, tmpColor); whiteShot.Dispose(); return final; - } + }*/ - backdrop.BackColor = Color.Black; + backdrop.BackColor = Color.Black; Application.DoEvents(); // Capture screenshot with black background Bitmap blackShot = CaptureScreenRegion(new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top)); - backdrop.Dispose(); - - Bitmap transparentImage = DifferentiateAlpha(whiteShot, blackShot); - if (data.CaptureMouse) + EmptyForm emptyForm = new EmptyForm(); + emptyForm.Show(); + //backdrop.Dispose(); + backdrop.BackColor = Color.White; + Application.DoEvents(); + + // Capture inactive screenshot with white background + emptyForm.Show(); + Bitmap whiteInactiveShot = CaptureScreenRegion(new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top)); + + /*if (data.Background == ScreenshotTask.BackgroundType.SolidColor) + { + backdrop.Dispose(); + if (data.CaptureMouse) + DrawCursorToBitmap(whiteShot, new Point(rct.Left, rct.Top)); + Bitmap final = CropEmptyEdges(whiteShot, tmpColor); + whiteShot.Dispose(); + return final; + }*/ + + backdrop.BackColor = Color.Black; + Application.DoEvents(); + + // Capture inactive screenshot with black background + emptyForm.Show(); + Bitmap blackInactiveShot = CaptureScreenRegion(new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top)); + + backdrop.Dispose(); + + Bitmap transparentImage = DifferentiateAlpha(whiteShot, blackShot, false); + Bitmap transparentInactiveImage = DifferentiateAlpha(whiteInactiveShot, blackInactiveShot, false); + Bitmap transparentWhiteImage = DifferentiateAlpha(whiteShot, blackShot, true); + Bitmap transparentWhiteInactiveImage = DifferentiateAlpha(whiteInactiveShot, blackInactiveShot, true); + if (data.CaptureMouse) DrawCursorToBitmap(transparentImage, new Point(rct.Left, rct.Top)); - transparentImage = CropEmptyEdges(transparentImage, Color.FromArgb(0, 0, 0, 0)); + Bitmap[] final = CropEmptyEdges(new[] { transparentImage, transparentInactiveImage, transparentWhiteImage, transparentWhiteInactiveImage }, Color.FromArgb(0, 0, 0, 0)); whiteShot.Dispose(); blackShot.Dispose(); - if (data.Background == ScreenshotTask.BackgroundType.Checkerboard) + //TODO: checkerboard support + /*if (data.Background == ScreenshotTask.BackgroundType.Checkerboard) { var final = new Bitmap(transparentImage.Width, transparentImage.Height, PixelFormat.Format24bppRgb); Graphics finalGraphics = Graphics.FromImage(final); @@ -417,9 +462,9 @@ private static unsafe Bitmap CaptureCompositeScreenshot(ref ScreenshotTask data) finalGraphics.Dispose(); transparentImage.Dispose(); return final; - } + }*/ // Returns a bitmap with transparency, calculated by differentiating the white and black screenshots - return transparentImage; + return final; } private static void DrawCursorToBitmap(Bitmap windowImage, Point offsetLocation) @@ -498,14 +543,14 @@ private static unsafe Bitmap GenerateChecker(int s) return b1; } - private static unsafe Bitmap CropEmptyEdges(Bitmap b1, Color trimColor) + private static unsafe Bitmap[] CropEmptyEdges(Bitmap[] b1, Color trimColor) { if (b1 == null) return null; - int sizeX = b1.Width; - int sizeY = b1.Height; - var b = new UnsafeBitmap(b1); + int sizeX = b1[0].Width; + int sizeY = b1[0].Height; + var b = new UnsafeBitmap(b1[0]); b.LockImage(); int left = -1; @@ -597,12 +642,24 @@ private static unsafe Bitmap CropEmptyEdges(Bitmap b1, Color trimColor) if (left >= right || top >= bottom) return null; - Bitmap final = b1.Clone(new Rectangle(left, top, right - left, bottom - top), b1.PixelFormat); - b1.Dispose(); + int rightSize = right - left; + int bottomSize = bottom - top; + if (rightSize % 2 == 1) + rightSize++; + if (bottomSize % 2 == 1) + bottomSize++; + + Bitmap[] final = new Bitmap[b1.Length]; + for(int i = 0; i < b1.Length; i++) + { + final[i] = b1[i].Clone(new Rectangle(left, top, rightSize, bottomSize), b1[i].PixelFormat); + b1[i].Dispose(); + } + return final; } - private static unsafe Bitmap DifferentiateAlpha(Bitmap whiteBitmap, Bitmap blackBitmap) + private static unsafe Bitmap DifferentiateAlpha(Bitmap whiteBitmap, Bitmap blackBitmap, bool useWhiteMath) { if (whiteBitmap == null || blackBitmap == null || whiteBitmap.Width != blackBitmap.Width || whiteBitmap.Height != blackBitmap.Height) return null; @@ -627,17 +684,23 @@ private static unsafe Bitmap DifferentiateAlpha(Bitmap whiteBitmap, Bitmap black pixelF->Alpha = ToByte((pixelB->Red - pixelA->Red + 255 + pixelB->Green - pixelA->Green + 255 + pixelB->Blue - pixelA->Blue + 255) / 3); if (pixelF->Alpha > 0) { - // Following math creates an image optimized to be displayed on a black background - pixelF->Red = ToByte(255 * pixelB->Red / pixelF->Alpha); - pixelF->Green = ToByte(255 * pixelB->Green / pixelF->Alpha); - pixelF->Blue = ToByte(255 * pixelB->Blue / pixelF->Alpha); - - // Following math creates an image optimized to be displayed on a white background - /* - pixelF->Red = ToByte(255 * (pixelA->Red + pixelF->Alpha - 255) / pixelF->Alpha); - pixelF->Green = ToByte(255 * (pixelA->Green + pixelF->Alpha - 255) / pixelF->Alpha); - pixelF->Blue = ToByte(255 * (pixelA->Blue + pixelF->Alpha - 255) / pixelF->Alpha); - */ + if (useWhiteMath) + { + // Following math creates an image optimized to be displayed on a white background + pixelF->Red = ToByte(255 * (pixelA->Red + pixelF->Alpha - 255) / pixelF->Alpha); + pixelF->Green = ToByte(255 * (pixelA->Green + pixelF->Alpha - 255) / pixelF->Alpha); + pixelF->Blue = ToByte(255 * (pixelA->Blue + pixelF->Alpha - 255) / pixelF->Alpha); + + } + else + { + // Following math creates an image optimized to be displayed on a black background + pixelF->Red = ToByte(255 * pixelB->Red / pixelF->Alpha); + pixelF->Green = ToByte(255 * pixelB->Green / pixelF->Alpha); + pixelF->Blue = ToByte(255 * pixelB->Blue / pixelF->Alpha); + } + + } if (empty && pixelF->Alpha > 0) empty = false; diff --git a/Settings.cs b/Settings.cs index f89d089..182de51 100644 --- a/Settings.cs +++ b/Settings.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 Copyright (C) 2012 Caleb Joseph diff --git a/Tray.cs b/Tray.cs index 24642a1..999d89f 100644 --- a/Tray.cs +++ b/Tray.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 AeroShot is free software: you can redistribute it and/or modify diff --git a/WindowsAPI.cs b/WindowsAPI.cs index c8e3fd6..73a8d95 100644 --- a/WindowsAPI.cs +++ b/WindowsAPI.cs @@ -1,4 +1,5 @@ /* AeroShot - Transparent screenshot utility for Windows + Copyright (C) 2021 Cvolton Copyright (C) 2015 toe_head2001 Copyright (C) 2012 Caleb Joseph diff --git a/app.config b/app.config new file mode 100644 index 0000000..2fa6e95 --- /dev/null +++ b/app.config @@ -0,0 +1,3 @@ + + +