/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: https://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/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.Drawing; using System.Drawing.Imaging; using Dapplo.Windows.Common.Extensions; using Dapplo.Windows.Common.Structs; namespace Greenshot.Base.Core { /// /// The interface for the FastBitmap /// public interface IFastBitmap : IDisposable { /// /// Get the color at x,y /// The returned Color object depends on the underlying pixel format /// /// int x /// int y /// Color Color GetColorAt(int x, int y); /// /// Set the color at the specified location /// /// int x /// int y /// Color void SetColorAt(int x, int y, Color color); /// /// Get the color at x,y /// The returned byte[] color depends on the underlying pixel format /// /// int x /// int y /// byte array void GetColorAt(int x, int y, byte[] color); /// /// Set the color at the specified location /// /// int x /// int y /// byte[] color void SetColorAt(int x, int y, byte[] color); /// /// Lock the bitmap /// void Lock(); /// /// Unlock the bitmap /// void Unlock(); /// /// Unlock the bitmap and get the underlying bitmap in one call /// /// Bitmap UnlockAndReturnBitmap(); /// /// Size of the underlying image /// NativeSize Size { get; } /// /// Height of the image area that this fastbitmap covers /// int Height { get; } /// /// Width of the image area that this fastbitmap covers /// int Width { get; } /// /// Top of the image area that this fastbitmap covers /// int Top { get; } /// /// Left of the image area that this fastbitmap covers /// int Left { get; } /// /// Right of the image area that this fastbitmap covers /// int Right { get; } /// /// Bottom of the image area that this fastbitmap covers /// int Bottom { get; } /// /// Does the underlying image need to be disposed /// bool NeedsDispose { get; set; } /// /// Returns if this FastBitmap has an alpha channel /// bool HasAlphaChannel { get; } /// /// Draw the stored bitmap to the destination bitmap at the supplied point /// /// Graphics /// NativePoint with location void DrawTo(Graphics graphics, NativePoint destination); /// /// Draw the stored Bitmap on the Destination bitmap with the specified rectangle /// Be aware that the stored bitmap will be resized to the specified rectangle!! /// /// Graphics /// NativeRect with destination void DrawTo(Graphics graphics, NativeRect destinationRect); /// /// Return true if the coordinates are inside the FastBitmap /// /// /// /// bool Contains(int x, int y); /// /// Set the bitmap resolution /// /// /// void SetResolution(float horizontal, float vertical); } /// /// This interface can be used for when offsetting is needed /// public interface IFastBitmapWithOffset : IFastBitmap { /// /// Return true if the coordinates are inside the FastBitmap /// /// /// /// new bool Contains(int x, int y); /// /// Set the color at the specified location, using offsetting so the original coordinates can be used /// /// int x /// int y /// Color color new void SetColorAt(int x, int y, Color color); /// /// Set the color at the specified location, using offsetting so the original coordinates can be used /// /// int x /// int y /// byte[] color new void SetColorAt(int x, int y, byte[] color); /// /// Get the color at x,y /// The returned Color object depends on the underlying pixel format /// /// int x /// int y /// Color new Color GetColorAt(int x, int y); /// /// Get the color at x,y, using offsetting so the original coordinates can be used /// The returned byte[] color depends on the underlying pixel format /// /// int x /// int y /// byte array new void GetColorAt(int x, int y, byte[] color); new int Left { get; set; } new int Top { get; set; } } /// /// This interface can be used for when clipping is needed /// public interface IFastBitmapWithClip : IFastBitmap { NativeRect Clip { get; set; } bool InvertClip { get; set; } /// /// Set the color at the specified location, this doesn't do anything if the location is excluded due to clipping /// /// int x /// int y /// Color color new void SetColorAt(int x, int y, Color color); /// /// Set the color at the specified location, this doesn't do anything if the location is excluded due to clipping /// /// int x /// int y /// byte[] color new void SetColorAt(int x, int y, byte[] color); /// /// Return true if the coordinates are inside the FastBitmap and not clipped /// /// /// /// new bool Contains(int x, int y); } /// /// This interface is implemented when there is a alpha-blending possibility /// public interface IFastBitmapWithBlend : IFastBitmap { Color BackgroundBlendColor { get; set; } Color GetBlendedColorAt(int x, int y); } /// /// The base class for the fast bitmap implementation /// public abstract unsafe class FastBitmap : IFastBitmapWithClip, IFastBitmapWithOffset { protected const int PixelformatIndexA = 3; protected const int PixelformatIndexR = 2; protected const int PixelformatIndexG = 1; protected const int PixelformatIndexB = 0; public const int ColorIndexR = 0; public const int ColorIndexG = 1; public const int ColorIndexB = 2; public const int ColorIndexA = 3; protected NativeRect Area; /// /// If this is set to true, the bitmap will be disposed when disposing the IFastBitmap /// public bool NeedsDispose { get; set; } public NativeRect Clip { get; set; } public bool InvertClip { get; set; } /// /// The bitmap for which the FastBitmap is creating access /// protected Bitmap Bitmap; protected BitmapData BmData; protected int Stride; /* bytes per pixel row */ protected bool BitsLocked; protected byte* Pointer; public static IFastBitmap Create(Bitmap source) { return Create(source, NativeRect.Empty); } public void SetResolution(float horizontal, float vertical) { Bitmap.SetResolution(horizontal, vertical); } /// /// Factory for creating a FastBitmap depending on the pixelformat of the source /// The supplied rectangle specifies the area for which the FastBitmap does its thing /// /// Bitmap to access /// NativeRect which specifies the area to have access to, can be NativeRect.Empty for the whole image /// IFastBitmap public static IFastBitmap Create(Bitmap source, NativeRect area) => source.PixelFormat switch { PixelFormat.Format8bppIndexed => new FastChunkyBitmap(source, area), PixelFormat.Format24bppRgb => new Fast24RgbBitmap(source, area), PixelFormat.Format32bppRgb => new Fast32RgbBitmap(source, area), PixelFormat.Format32bppArgb => new Fast32ArgbBitmap(source, area), PixelFormat.Format32bppPArgb => new Fast32ArgbBitmap(source, area), _ => throw new NotSupportedException($"Not supported PixelFormat {source.PixelFormat}") }; /// /// Factory for creating a FastBitmap as a destination for the source /// /// Bitmap to clone /// new PixelFormat /// IFastBitmap public static IFastBitmap CreateCloneOf(Image source, PixelFormat pixelFormat) { return CreateCloneOf(source, pixelFormat, NativeRect.Empty); } /// /// Factory for creating a FastBitmap as a destination for the source /// /// Bitmap to clone /// Area of the bitmap to access, can be NativeRect.Empty for the whole /// IFastBitmap public static IFastBitmap CreateCloneOf(Image source, NativeRect area) { return CreateCloneOf(source, PixelFormat.DontCare, area); } /// /// Factory for creating a FastBitmap as a destination for the source /// /// Bitmap to clone /// Pixelformat of the cloned bitmap /// Area of the bitmap to access, can be NativeRect.Empty for the whole /// IFastBitmap public static IFastBitmap CreateCloneOf(Image source, PixelFormat pixelFormat, NativeRect area) { Bitmap destination = ImageHelper.CloneArea(source, area, pixelFormat); FastBitmap fastBitmap = Create(destination) as FastBitmap; if (fastBitmap != null) { fastBitmap.NeedsDispose = true; fastBitmap.Left = area.Left; fastBitmap.Top = area.Top; } return fastBitmap; } /// /// Factory for creating a FastBitmap as a destination /// /// NativeSize /// PixelFormat /// Color /// IFastBitmap public static IFastBitmap CreateEmpty(NativeSize newSize, PixelFormat pixelFormat, Color backgroundColor) { Bitmap destination = ImageHelper.CreateEmpty(newSize.Width, newSize.Height, pixelFormat, backgroundColor, 96f, 96f); IFastBitmap fastBitmap = Create(destination); fastBitmap.NeedsDispose = true; return fastBitmap; } /// /// Constructor which stores the image and locks it when called /// /// Bitmap /// NativeRect protected FastBitmap(Bitmap bitmap, NativeRect area) { Bitmap = bitmap; var bitmapArea = new NativeRect(NativePoint.Empty, bitmap.Size); if (area != NativeRect.Empty) { area = area.Intersect(bitmapArea); Area = area; } else { Area = bitmapArea; } // As the lock takes care that only the specified area is made available we need to calculate the offset Left = area.Left; Top = area.Top; // Default cliping is done to the area without invert Clip = Area; InvertClip = false; // Always lock, so we don't need to do this ourselves Lock(); } /// /// Return the size of the image /// public NativeSize Size { get { if (Area == NativeRect.Empty) { return Bitmap.Size; } return Area.Size; } } /// /// Return the width of the image /// public int Width { get { if (Area == NativeRect.Empty) { return Bitmap.Width; } return Area.Width; } } /// /// Return the height of the image /// public int Height { get { if (Area == NativeRect.Empty) { return Bitmap.Height; } return Area.Height; } } private int _left; /// /// Return the left of the fastbitmap, this is also used as an offset /// public int Left { get { return 0; } set { _left = value; } } /// /// Return the left of the fastbitmap, this is also used as an offset /// int IFastBitmapWithOffset.Left { get { return _left; } set { _left = value; } } private int _top; /// /// Return the top of the fastbitmap, this is also used as an offset /// public int Top { get { return 0; } set { _top = value; } } /// /// Return the top of the fastbitmap, this is also used as an offset /// int IFastBitmapWithOffset.Top { get { return _top; } set { _top = value; } } /// /// Return the right of the fastbitmap /// public int Right => Left + Width; /// /// Return the bottom of the fastbitmap /// public int Bottom => Top + Height; /// /// Returns the underlying bitmap, unlocks it and prevents that it will be disposed /// public Bitmap UnlockAndReturnBitmap() { if (BitsLocked) { Unlock(); } NeedsDispose = false; return Bitmap; } public virtual bool HasAlphaChannel => false; /// /// Destructor /// ~FastBitmap() { Dispose(false); } /// /// The public accessible Dispose /// Will call the GarbageCollector to SuppressFinalize, preventing being cleaned twice /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // The bulk of the clean-up code is implemented in Dispose(bool) /// /// 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) { Unlock(); if (disposing) { if (Bitmap != null && NeedsDispose) { Bitmap.Dispose(); } } Bitmap = null; BmData = null; Pointer = null; } /// /// Lock the bitmap so we have direct access to the memory /// public void Lock() { if (Width <= 0 || Height <= 0 || BitsLocked) { return; } BmData = Bitmap.LockBits(Area, ImageLockMode.ReadWrite, Bitmap.PixelFormat); BitsLocked = true; IntPtr scan0 = BmData.Scan0; Pointer = (byte*) (void*) scan0; Stride = BmData.Stride; } /// /// Unlock the System Memory /// public void Unlock() { if (BitsLocked) { Bitmap.UnlockBits(BmData); BitsLocked = false; } } /// /// Draw the stored bitmap to the destination bitmap at the supplied point /// /// /// public void DrawTo(Graphics graphics, NativePoint destination) { DrawTo(graphics, new NativeRect(destination, Area.Size)); } /// /// Draw the stored Bitmap on the Destination bitmap with the specified rectangle /// Be aware that the stored bitmap will be resized to the specified rectangle!! /// /// /// NativeRect public void DrawTo(Graphics graphics, NativeRect destinationRect) { // Make sure this.bitmap is unlocked, if it was locked bool isLocked = BitsLocked; if (isLocked) { Unlock(); } graphics.DrawImage(Bitmap, destinationRect, Area, GraphicsUnit.Pixel); } /// /// returns true if x & y are inside the FastBitmap /// /// /// /// true if x & y are inside the FastBitmap public bool Contains(int x, int y) { return Area.Contains(x - Left, y - Top); } public abstract Color GetColorAt(int x, int y); public abstract void SetColorAt(int x, int y, Color color); public abstract void GetColorAt(int x, int y, byte[] color); public abstract void SetColorAt(int x, int y, byte[] color); bool IFastBitmapWithClip.Contains(int x, int y) { bool contains = Clip.Contains(x, y); if (InvertClip) { return !contains; } else { return contains; } } void IFastBitmapWithClip.SetColorAt(int x, int y, byte[] color) { bool contains = Clip.Contains(x, y); if ((InvertClip && contains) || (!InvertClip && !contains)) { return; } SetColorAt(x, y, color); } void IFastBitmapWithClip.SetColorAt(int x, int y, Color color) { bool contains = Clip.Contains(x, y); if ((InvertClip && contains) || (!InvertClip && !contains)) { return; } SetColorAt(x, y, color); } /// /// returns true if x & y are inside the FastBitmap /// /// /// /// true if x & y are inside the FastBitmap bool IFastBitmapWithOffset.Contains(int x, int y) { return Area.Contains(x - Left, y - Top); } Color IFastBitmapWithOffset.GetColorAt(int x, int y) { x -= _left; y -= _top; return GetColorAt(x, y); } void IFastBitmapWithOffset.GetColorAt(int x, int y, byte[] color) { x -= _left; y -= _top; GetColorAt(x, y, color); } void IFastBitmapWithOffset.SetColorAt(int x, int y, byte[] color) { x -= _left; y -= _top; SetColorAt(x, y, color); } void IFastBitmapWithOffset.SetColorAt(int x, int y, Color color) { x -= _left; y -= _top; SetColorAt(x, y, color); } } /// /// This is the implementation of the FastBitmap for the 8BPP pixelformat /// public unsafe class FastChunkyBitmap : FastBitmap { // Used for indexed images private readonly Color[] _colorEntries; private readonly Dictionary _colorCache = new Dictionary(); public FastChunkyBitmap(Bitmap source, NativeRect area) : base(source, area) { _colorEntries = Bitmap.Palette.Entries; } /// /// Get the color from the specified location /// /// /// /// Color public override Color GetColorAt(int x, int y) { int offset = x + (y * Stride); byte colorIndex = Pointer[offset]; return _colorEntries[colorIndex]; } /// /// Get the color from the specified location into the specified array /// /// /// /// byte[4] as reference public override void GetColorAt(int x, int y, byte[] color) { throw new NotImplementedException("No performance gain!"); } /// /// Set the color at the specified location from the specified array /// /// /// /// byte[4] as reference public override void SetColorAt(int x, int y, byte[] color) { throw new NotImplementedException("No performance gain!"); } /// /// Get the color-index from the specified location /// /// /// /// byte with index public byte GetColorIndexAt(int x, int y) { int offset = x + (y * Stride); return Pointer[offset]; } /// /// Set the color-index at the specified location /// /// /// /// public void SetColorIndexAt(int x, int y, byte colorIndex) { int offset = x + (y * Stride); Pointer[offset] = colorIndex; } /// /// Set the supplied color at the specified location. /// Throws an ArgumentException if the color is not in the palette /// /// /// /// Color to set public override void SetColorAt(int x, int y, Color color) { int offset = x + (y * Stride); if (!_colorCache.TryGetValue(color, out var colorIndex)) { bool foundColor = false; for (colorIndex = 0; colorIndex < _colorEntries.Length; colorIndex++) { if (color == _colorEntries[colorIndex]) { _colorCache.Add(color, colorIndex); foundColor = true; break; } } if (!foundColor) { throw new ArgumentException("No such color!"); } } Pointer[offset] = colorIndex; } } /// /// This is the implementation of the IFastBitmap for 24 bit images (no Alpha) /// public unsafe class Fast24RgbBitmap : FastBitmap { public Fast24RgbBitmap(Bitmap source, NativeRect area) : base(source, area) { } /// /// Retrieve the color at location x,y /// Before the first time this is called the Lock() should be called once! /// /// X coordinate /// Y Coordinate /// Color public override Color GetColorAt(int x, int y) { int offset = (x * 3) + (y * Stride); return Color.FromArgb(255, Pointer[PixelformatIndexR + offset], Pointer[PixelformatIndexG + offset], Pointer[PixelformatIndexB + offset]); } /// /// Set the color at location x,y /// Before the first time this is called the Lock() should be called once! /// /// /// /// public override void SetColorAt(int x, int y, Color color) { int offset = (x * 3) + (y * Stride); Pointer[PixelformatIndexR + offset] = color.R; Pointer[PixelformatIndexG + offset] = color.G; Pointer[PixelformatIndexB + offset] = color.B; } /// /// Get the color from the specified location into the specified array /// /// /// /// byte[4] as reference (r,g,b) public override void GetColorAt(int x, int y, byte[] color) { int offset = (x * 3) + (y * Stride); color[PixelformatIndexR] = Pointer[PixelformatIndexR + offset]; color[PixelformatIndexG] = Pointer[PixelformatIndexG + offset]; color[PixelformatIndexB] = Pointer[PixelformatIndexB + offset]; } /// /// Set the color at the specified location from the specified array /// /// /// /// byte[4] as reference (r,g,b) public override void SetColorAt(int x, int y, byte[] color) { int offset = (x * 3) + (y * Stride); Pointer[PixelformatIndexR + offset] = color[PixelformatIndexR]; Pointer[PixelformatIndexG + offset] = color[PixelformatIndexG]; Pointer[PixelformatIndexB + offset] = color[PixelformatIndexB]; } } /// /// This is the implementation of the IFastBitmap for 32 bit images (no Alpha) /// public unsafe class Fast32RgbBitmap : FastBitmap { public Fast32RgbBitmap(Bitmap source, NativeRect area) : base(source, area) { } /// /// Retrieve the color at location x,y /// Before the first time this is called the Lock() should be called once! /// /// X coordinate /// Y Coordinate /// Color public override Color GetColorAt(int x, int y) { int offset = (x * 4) + (y * Stride); return Color.FromArgb(255, Pointer[PixelformatIndexR + offset], Pointer[PixelformatIndexG + offset], Pointer[PixelformatIndexB + offset]); } /// /// Set the color at location x,y /// Before the first time this is called the Lock() should be called once! /// /// /// /// public override void SetColorAt(int x, int y, Color color) { int offset = (x * 4) + (y * Stride); Pointer[PixelformatIndexR + offset] = color.R; Pointer[PixelformatIndexG + offset] = color.G; Pointer[PixelformatIndexB + offset] = color.B; } /// /// Get the color from the specified location into the specified array /// /// /// /// byte[4] as reference (a,r,g,b) public override void GetColorAt(int x, int y, byte[] color) { int offset = (x * 4) + (y * Stride); color[ColorIndexR] = Pointer[PixelformatIndexR + offset]; color[ColorIndexG] = Pointer[PixelformatIndexG + offset]; color[ColorIndexB] = Pointer[PixelformatIndexB + offset]; } /// /// Set the color at the specified location from the specified array /// /// /// /// byte[4] as reference (r,g,b) public override void SetColorAt(int x, int y, byte[] color) { int offset = (x * 4) + (y * Stride); Pointer[PixelformatIndexR + offset] = color[ColorIndexR]; // R Pointer[PixelformatIndexG + offset] = color[ColorIndexG]; Pointer[PixelformatIndexB + offset] = color[ColorIndexB]; } } /// /// This is the implementation of the IFastBitmap for 32 bit images with Alpha /// public unsafe class Fast32ArgbBitmap : FastBitmap, IFastBitmapWithBlend { public override bool HasAlphaChannel => true; public Color BackgroundBlendColor { get; set; } public Fast32ArgbBitmap(Bitmap source, NativeRect area) : base(source, area) { BackgroundBlendColor = Color.White; } /// /// Retrieve the color at location x,y /// /// X coordinate /// Y Coordinate /// Color public override Color GetColorAt(int x, int y) { int offset = (x * 4) + (y * Stride); return Color.FromArgb(Pointer[PixelformatIndexA + offset], Pointer[PixelformatIndexR + offset], Pointer[PixelformatIndexG + offset], Pointer[PixelformatIndexB + offset]); } /// /// Set the color at location x,y /// Before the first time this is called the Lock() should be called once! /// /// /// /// public override void SetColorAt(int x, int y, Color color) { int offset = (x * 4) + (y * Stride); Pointer[PixelformatIndexA + offset] = color.A; Pointer[PixelformatIndexR + offset] = color.R; Pointer[PixelformatIndexG + offset] = color.G; Pointer[PixelformatIndexB + offset] = color.B; } /// /// Get the color from the specified location into the specified array /// /// /// /// byte[4] as reference (r,g,b,a) public override void GetColorAt(int x, int y, byte[] color) { int offset = (x * 4) + (y * Stride); color[ColorIndexR] = Pointer[PixelformatIndexR + offset]; color[ColorIndexG] = Pointer[PixelformatIndexG + offset]; color[ColorIndexB] = Pointer[PixelformatIndexB + offset]; color[ColorIndexA] = Pointer[PixelformatIndexA + offset]; } /// /// Set the color at the specified location from the specified array /// /// /// /// byte[4] as reference (r,g,b,a) public override void SetColorAt(int x, int y, byte[] color) { int offset = (x * 4) + (y * Stride); Pointer[PixelformatIndexR + offset] = color[ColorIndexR]; // R Pointer[PixelformatIndexG + offset] = color[ColorIndexG]; Pointer[PixelformatIndexB + offset] = color[ColorIndexB]; Pointer[PixelformatIndexA + offset] = color[ColorIndexA]; } /// /// Retrieve the color, without alpha (is blended), at location x,y /// Before the first time this is called the Lock() should be called once! /// /// X coordinate /// Y Coordinate /// Color public Color GetBlendedColorAt(int x, int y) { int offset = (x * 4) + (y * Stride); int a = Pointer[PixelformatIndexA + offset]; int red = Pointer[PixelformatIndexR + offset]; int green = Pointer[PixelformatIndexG + offset]; int blue = Pointer[PixelformatIndexB + offset]; if (a < 255) { // As the request is to get without alpha, we blend. int rem = 255 - a; red = (red * a + BackgroundBlendColor.R * rem) / 255; green = (green * a + BackgroundBlendColor.G * rem) / 255; blue = (blue * a + BackgroundBlendColor.B * rem) / 255; } return Color.FromArgb(255, red, green, blue); } } }