Readded black-bar removal

This commit is contained in:
Darth Affe 2023-09-10 22:44:24 +02:00
parent bcb92eb422
commit 57462e9717
3 changed files with 189 additions and 141 deletions

View File

@ -0,0 +1,188 @@
namespace ScreenCapture.NET;
/// <summary>
/// Helper-class for black-bar removal.
/// </summary>
public static class BlackBarDetection
{
#region IImage
/// <summary>
/// Create an image with black bars removed
/// </summary>
/// <param name="image">The image the bars are removed from.</param>
/// <param name="threshold">The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)</param>
/// <param name="removeTop">A bool indicating if black bars should be removed at the top of the image.</param>
/// <param name="removeBottom">A bool indicating if black bars should be removed at the bottom of the image.</param>
/// <param name="removeLeft">A bool indicating if black bars should be removed on the left side of the image.</param>
/// <param name="removeRight">A bool indicating if black bars should be removed on the right side of the image.</param>
/// <returns>The image with black bars removed.</returns>
public static IImage RemoveBlackBars(this IImage image, int threshold = 0, bool removeTop = true, bool removeBottom = true, bool removeLeft = true, bool removeRight = true)
{
int top = removeTop ? CalculateTop(image, threshold) : 0;
int bottom = removeBottom ? CalculateBottom(image, threshold) : image.Height;
int left = removeLeft ? CalculateLeft(image, threshold) : 0;
int right = removeRight ? CalculateRight(image, threshold) : image.Width;
return image[left, top, right - left, bottom - top];
}
private static int CalculateTop(IImage image, int threshold)
{
IImage.IImageRows rows = image.Rows;
for (int y = 0; y < rows.Count; y++)
{
IImage.IImageRow row = rows[y];
foreach (IColor color in row)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return y;
}
}
return 0;
}
private static int CalculateBottom(IImage image, int threshold)
{
IImage.IImageRows rows = image.Rows;
for (int y = rows.Count - 1; y >= 0; y--)
{
IImage.IImageRow row = rows[y];
foreach (IColor color in row)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return y;
}
}
return rows.Count;
}
private static int CalculateLeft(IImage image, int threshold)
{
IImage.IImageColumns columns = image.Columns;
for (int x = 0; x < columns.Count; x++)
{
IImage.IImageColumn column = columns[x];
foreach (IColor color in column)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return x;
}
}
return 0;
}
private static int CalculateRight(IImage image, int threshold)
{
IImage.IImageColumns columns = image.Columns;
for (int x = columns.Count - 1; x >= 0; x--)
{
IImage.IImageColumn column = columns[x];
foreach (IColor color in column)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return x;
}
}
return columns.Count;
}
#endregion
#region RefImage
/// <summary>
/// Create an image with black bars removed
/// </summary>
/// <param name="image">The image the bars are removed from.</param>
/// <param name="threshold">The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)</param>
/// <param name="removeTop">A bool indicating if black bars should be removed at the top of the image.</param>
/// <param name="removeBottom">A bool indicating if black bars should be removed at the bottom of the image.</param>
/// <param name="removeLeft">A bool indicating if black bars should be removed on the left side of the image.</param>
/// <param name="removeRight">A bool indicating if black bars should be removed on the right side of the image.</param>
/// <returns>The image with black bars removed.</returns>
public static RefImage<TColor> RemoveBlackBars<TColor>(this RefImage<TColor> image, int threshold = 0, bool removeTop = true, bool removeBottom = true, bool removeLeft = true, bool removeRight = true)
where TColor : struct, IColor
{
int top = removeTop ? CalculateTop(image, threshold) : 0;
int bottom = removeBottom ? CalculateBottom(image, threshold) : image.Height;
int left = removeLeft ? CalculateLeft(image, threshold) : 0;
int right = removeRight ? CalculateRight(image, threshold) : image.Width;
return image[left, top, right - left, bottom - top];
}
private static int CalculateTop<TColor>(this RefImage<TColor> image, int threshold)
where TColor : struct, IColor
{
RefImage<TColor>.ImageRows rows = image.Rows;
for (int y = 0; y < rows.Count; y++)
{
ReadOnlyRefEnumerable<TColor> row = rows[y];
foreach (TColor color in row)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return y;
}
}
return 0;
}
private static int CalculateBottom<TColor>(this RefImage<TColor> image, int threshold)
where TColor : struct, IColor
{
RefImage<TColor>.ImageRows rows = image.Rows;
for (int y = rows.Count - 1; y >= 0; y--)
{
ReadOnlyRefEnumerable<TColor> row = rows[y];
foreach (TColor color in row)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return y;
}
}
return rows.Count;
}
private static int CalculateLeft<TColor>(this RefImage<TColor> image, int threshold)
where TColor : struct, IColor
{
RefImage<TColor>.ImageColumns columns = image.Columns;
for (int x = 0; x < columns.Count; x++)
{
ReadOnlyRefEnumerable<TColor> column = columns[x];
foreach (TColor color in column)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return x;
}
}
return 0;
}
private static int CalculateRight<TColor>(this RefImage<TColor> image, int threshold)
where TColor : struct, IColor
{
RefImage<TColor>.ImageColumns columns = image.Columns;
for (int x = columns.Count - 1; x >= 0; x--)
{
ReadOnlyRefEnumerable<TColor> column = columns[x];
foreach (TColor color in column)
{
if ((color.R > threshold) || (color.G > threshold) || (color.B > threshold))
return x;
}
}
return columns.Count;
}
#endregion
}

View File

@ -1,141 +0,0 @@
// ReSharper disable MemberCanBePrivate.Global
using System;
namespace ScreenCapture.NET;
/// <summary>
/// Represents the configuration for the detection and removal of black bars around the screen image.
/// </summary>
public sealed class BlackBarDetection
{
#region Properties & Fields
private readonly ICaptureZone _captureZone;
private int? _top;
/// <summary>
/// Gets the size of the detected black bar at the top of the image.
/// </summary>
public int Top => _top ??= CalculateTop();
private int? _bottom;
/// <summary>
/// Gets the size of the detected black bar at the bottom of the image.
/// </summary>
public int Bottom => _bottom ??= CalculateBottom();
private int? _left;
/// <summary>
/// Gets the size of the detected black bar at the left of the image.
/// </summary>
public int Left => _left ??= CalculateLeft();
private int? _right;
/// <summary>
/// Gets the size of the detected black bar at the right of the image.
/// </summary>
public int Right => _right ??= CalculateRight();
private int _theshold = 0;
/// <summary>
/// Gets or sets the threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.) (default 0)
/// </summary>
public int Threshold
{
get => _theshold;
set
{
_theshold = value;
InvalidateCache();
}
}
#endregion
#region Constructors
internal BlackBarDetection(ICaptureZone captureZone)
{
this._captureZone = captureZone;
}
#endregion
#region Methods
/// <summary>
/// Invalidates the cached values and recalculates <see cref="Top"/>, <see cref="Bottom"/>, <see cref="Left"/> and <see cref="Right"/>.
/// </summary>
public void InvalidateCache()
{
_top = null;
_bottom = null;
_left = null;
_right = null;
}
private int CalculateTop()
{
//int threshold = Threshold;
//int stride = _captureZone.Stride;
//for (int row = 0; row < _captureZone.Height; row++)
//{
// Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
// for (int i = 0; i < data.Length; i += 4)
// if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
// return row;
//}
return 0;
}
private int CalculateBottom()
{
//int threshold = Threshold;
//int stride = _captureZone.Stride;
//for (int row = _captureZone.Height - 1; row >= 0; row--)
//{
// Span<byte> data = new(_captureZone.Buffer, row * stride, stride);
// for (int i = 0; i < data.Length; i += 4)
// if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold))
// return (_captureZone.Height - 1) - row;
//}
return 0;
}
private int CalculateLeft()
{
//int threshold = Threshold;
//int stride = _captureZone.Stride;
//byte[] buffer = _captureZone.Buffer;
//for (int column = 0; column < _captureZone.Width; column++)
// for (int row = 0; row < _captureZone.Height; row++)
// {
// int offset = (stride * row) + (column * 4);
// if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
// return column;
// }
return 0;
}
private int CalculateRight()
{
//int threshold = Threshold;
//int stride = _captureZone.Stride;
//byte[] buffer = _captureZone.Buffer;
//for (int column = _captureZone.Width - 1; column >= 0; column--)
// for (int row = 0; row < _captureZone.Height; row++)
// {
// int offset = (stride * row) + (column * 4);
// if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold))
// return (_captureZone.Width - 1) - column;
// }
return 0;
}
#endregion
}

View File

@ -2,6 +2,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=communitytoolkit_002Ehighperformance/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=communitytoolkit_002Ehighperformance/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=directx/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=directx/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generic/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generic/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helper/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helper/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean>