我有一个手写调查的位图对象(见下面的调查图片),其中包含各种复选框。我正在使用一种算法来比较空白、未标记复选框的位图与同一复选框(可能会或可能不会被标记)的位图,以确定复选框是否已被标记。
是否可以将主要调查位图分解为较小的位图对象?例如,对于下面的问题 14,我想在给定每个复选框的 startX、endX、startY 和 endY 位置的情况下用红色方块制作更小的 Bitmap 对象。
我正在开发一个名为 Transparency Maker 的开源程序,它可以帮助您做到这一点,或者至少为您提供一些入门指南。
我的项目读取位图(.png 或 .jpg)并创建一个像素数据库。实际上它是一个 PixelInformation 对象的列表。
LoadPixelDatabase 方法
我的 Windows 窗体应用程序有一个名为 Canvas 的 PictureBox 用于背景图像。您可以轻松地将图像作为参数传递。
using System.Drawing;
using System.Drawing.Imaging;
public void LoadPixelDatabase()
{
// if we do not have a BackgroundImage yet
if (this.Canvas.BackgroundImage == null)
{
// to do: Show message
// bail
return;
}
// Create a Bitmap from the Source image
Bitmap source = new Bitmap(this.Canvas.BackgroundImage);
// Code To Lockbits
BitmapData bitmapData = source.LockBits(new Rectangle(0, 0, source.Width,
source.Height), ImageLockMode.ReadWrite, source.PixelFormat);
IntPtr pointer = bitmapData.Scan0;
int size = Math.Abs(bitmapData.Stride) * source.Height;
byte[] pixels = new byte[size];
Marshal.Copy(pointer, pixels, 0, size);
// End Code To Lockbits
// Marshal.Copy(pixels,0,pointer, size);
source.UnlockBits(bitmapData);
// test only
int length = pixels.Length;
// Create a new instance of a 'PixelDatabase' object.
this.PixelDatabase = new PixelDatabase();
// locals
Color color = Color.FromArgb(0, 0, 0);
int red = 0;
int green = 0;
int blue = 0;
int alpha = 0;
// variables to hold height and width
int width = source.Width;
int height = source.Height;
int x = -1;
int y = 0;
// Iterating the pixel array, every 4th byte is a new pixel,faster than GetPixel
for (int a = 0; a < pixels.Length; a = a + 4)
{
// increment the value for x
x++;
// every new column
if (x >= width)
{
// reset x
x = 0;
// Increment the value for y
y++;
}
// get the values for r, g, and blue
blue = pixels[a];
green = pixels[a + 1];
red = pixels[a + 2];
alpha = pixels[a + 3];
// create a color
color = Color.FromArgb(alpha, red, green, blue);
// Add this point
PixelInformation pixelInformation = this.PixelDatabase.AddPixel(color, x, y);
}
// Create a DirectBitmap
this.DirectBitmap = new DirectBitmap(source.Width, source.Height);
// Now we must copy over the Pixels from the PixelDatabase to the DirectBitmap
if ((this.HasPixelDatabase) && (ListHelper.HasOneOrMoreItems(this.PixelDatabase.Pixels)))
{
// iterate the pixels
foreach (PixelInformation pixel in this.PixelDatabase.Pixels)
{
// Set the pixel at this spot
DirectBitmap.SetPixel(pixel.X, pixel.Y, pixel.Color);
}
}
}
我的应用程序可以帮助您的方式是,加载 PixelDatabase 后,您可以执行 LinqQueries,例如:
// Get the pixels in the X range
pixels = pixels.Where(x => x.X >= MinValue && x.X <= MaxValue).ToList();
// Get the Y range
pixels = pixels.Where(x => x.Y >= MinValue && x.Y <= MaxValue).ToList();
(我知道上面可以写成 1 行,这里很难发布)。
获得像素后,您可以创建新图像:
Image image = new Bitmap(width, height);
为新图像创建另一个 DirectBitmap,然后将上述查询中的像素复制到新图像中并保存。
// Save the bitmap
bitmap.Save(fileName);
像素数据库.cs
#region using statements
using DataJuggler.Core.UltimateHelper;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#endregion
namespace TransparencyMaker.Objects
{
#region class PixelDatabase
/// <summary>
/// This class represents a collection of PixelInformation objects
/// </summary>
public class PixelDatabase
{
#region Private Variables
private List<PixelInformation> pixels;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a PixelDatabase object
/// </summary>
public PixelDatabase()
{
// Create a new collection of 'PixelInformation' objects.
this.Pixels = new List<PixelInformation>();
}
#endregion
#region Methods
#region AddPixel(Color color, int x, int y)
/// <summary>
/// method returns the Pixel
/// </summary>
public PixelInformation AddPixel(Color color, int x, int y)
{
// Create a pixe
PixelInformation pixel = new PixelInformation();
// Set the color
pixel.Color = color;
// Set the values for x and y
pixel.X = x;
pixel.Y = y;
/// The Index is set before the count increments when this item is added
pixel.Index = this.Pixels.Count;
// Add this pixel
this.Pixels.Add(pixel);
// return value
return pixel;
}
#endregion
#endregion
#region Properties
#region HasOneOrMorePixels
/// <summary>
/// This property returns true if this object has one or more 'Pixels'.
/// </summary>
public bool HasOneOrMorePixels
{
get
{
// initial value
bool hasOneOrMorePixels = ((this.HasPixels) && (this.Pixels.Count > 0));
// return value
return hasOneOrMorePixels;
}
}
#endregion
#region HasPixels
/// <summary>
/// This property returns true if this object has a 'Pixels'.
/// </summary>
public bool HasPixels
{
get
{
// initial value
bool hasPixels = (this.Pixels != null);
// return value
return hasPixels;
}
}
#endregion
#region Pixels
/// <summary>
/// This property gets or sets the value for 'Pixels'.
/// </summary>
public List<PixelInformation> Pixels
{
get { return pixels; }
set { pixels = value; }
}
#endregion
#endregion
}
#endregion
}
像素信息.cs
#region using statements
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#endregion
namespace TransparencyMaker.Objects
{
#region class PixelInformation
/// <summary>
/// This class is used to contain information about a pixel, and
/// </summary>
public class PixelInformation
{
#region Private Variables
private int index;
private Color color;
private int x;
private int y;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a PixelInformationObject
/// </summary>
public PixelInformation()
{
// Perform initializations for this object
Init();
}
#endregion
#region Methods
#region Init()
/// <summary>
/// This method performs initializations for this object.
/// </summary>
public void Init()
{
}
#endregion
#region ToString()
/// <summary>
/// method returns the String
/// </summary>
public override string ToString()
{
// Create a new instance of a 'StringBuilder' object.
StringBuilder sb = new StringBuilder();
// Append the string
sb.Append("R:");
sb.Append(Red);
sb.Append("G:");
sb.Append(Green);
sb.Append("B:");
sb.Append(Blue);
sb.Append("T:");
sb.Append(Total);
// set the return value
string toString = sb.ToString();
// return value
return toString;
}
#endregion
#endregion
#region Properties
#region Alpha
/// <summary>
/// This property gets or sets the value for 'Alpha'.
/// </summary>
public int Alpha
{
get
{
// initial value
int alpha = Color.A;
// return value
return alpha;
}
}
#endregion
#region Blue
/// <summary>
/// This property gets or sets the value for 'Blue'.
/// </summary>
public int Blue
{
get
{
// initial value
int blue = Color.B;
// return value
return blue;
}
}
#endregion
#region BlueGreen
/// <summary>
/// This read only property returns the value for 'BlueGreen'.
/// </summary>
public int BlueGreen
{
get
{
// initial value
int blueGreen = Blue + Green;
// return value
return blueGreen;
}
}
#endregion
#region BlueRed
/// <summary>
/// This read only property returns the value for 'BlueRed'.
/// </summary>
public int BlueRed
{
get
{
// initial value
int blueRed = Blue + Red;
// return value
return blueRed;
}
}
#endregion
#region Color
/// <summary>
/// This property gets or sets the value for 'Color'.
/// </summary>
public Color Color
{
get { return color; }
set { color = value; }
}
#endregion
#region Green
/// <summary>
/// This property gets or sets the value for 'Green'.
/// </summary>
public int Green
{
get
{
// initial value
int green = Color.G;
// return value
return green;
}
}
#endregion
#region GreenRed
/// <summary>
/// This read only property returns the value for 'GreenRed'.
/// </summary>
public int GreenRed
{
get
{
// initial value
int greenRed = Green + Red;
// return value
return greenRed;
}
}
#endregion
#region Index
/// <summary>
/// This property gets or sets the value for 'Index'.
/// </summary>
public int Index
{
get { return index; }
set { index = value; }
}
#endregion
#region Red
/// <summary>
/// This property gets or sets the value for 'Red'.
/// </summary>
public int Red
{
get
{
// initial value
int red = this.Color.R;
// return value
return red;
}
}
#endregion
#region Total
/// <summary>
/// This read only property returns the value for 'Total'.
/// </summary>
public int Total
{
get
{
// initial value
int total = Red + Green + Blue;
// return value
return total;
}
}
#endregion
#region X
/// <summary>
/// This property gets or sets the value for 'X'.
/// </summary>
public int X
{
get { return x; }
set { x = value; }
}
#endregion
#region Y
/// <summary>
/// This property gets or sets the value for 'Y'.
/// </summary>
public int Y
{
get { return y; }
set { y = value; }
}
#endregion
#endregion
}
#endregion
}
DirectBitmap.cs
这里的这个类叫做 DirectBitmap,不是我写的,但我希望我知道作者是谁,以表扬他们,因为它大大加快了我的应用程序的速度。
#region using statements
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using TransparencyMaker.Objects;
#endregion
namespace TransparencyMaker.Util
{
#region class DirectBitmap
/// <summary>
/// This class is used as a faster alternative to GetPixel and SetPixel
/// </summary>
public class DirectBitmap : IDisposable
{
#region Private Variables
private History history;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a 'DirectBitmap' object.
/// </summary>
public DirectBitmap(int width, int height)
{
Width = width;
Height = height;
Bits = new Int32[width * height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
}
#endregion
#region Methods
#region Dispose()
/// <summary>
/// method Dispose
/// </summary>
public void Dispose()
{
if (Disposed) return;
Disposed = true;
Bitmap.Dispose();
BitsHandle.Free();
}
#endregion
#region GetPixel(int x, int y)
/// <summary>
/// method Get Pixel
/// </summary>
public Color GetPixel(int x, int y)
{
int index = x + (y * Width);
int col = Bits[index];
Color result = Color.FromArgb(col);
return result;
}
#endregion
#region HandleHistory(int x, int y, Guid historyId, Color previousColor)
/// <summary>
/// This method Handle History
/// </summary>
public void HandleHistory(int x, int y, Guid historyId, Color previousColor)
{
// If the History object exists
if ((!this.HasHistory) || (History.Id != historyId) && (historyId != Guid.Empty))
{
// here a new History object is created and the pixels are added to it instead
this.History = new History(historyId);
}
// If the History object exists
if (this.HasHistory)
{
// Create a new instance of a 'PixelInformation' object.
PixelInformation pixel = new PixelInformation();
pixel.X = x;
pixel.Y = y;
pixel.Color = previousColor;
// Add this pixel to history
this.History.ChangedPixels.Add(pixel);
}
}
#endregion
#region SetPixel(int x, int y, Color color)
/// <summary>
/// method Set Pixel
/// </summary>
public void SetPixel(int x, int y, Color color)
{
int index = x + (y * Width);
int col = color.ToArgb();
Bits[index] = col;
}
#endregion
#region SetPixel(int x, int y, Color color, Guid historyId, Color prevoiusColor)
/// <summary>
/// This method Sets a Pixel and it includes a historyId so any changes are stored in history
/// </summary>
public void SetPixel(int x, int y, Color color, Guid historyId, Color prevoiusColor)
{
// history has to be set before the pixel is set
// Handle the history
HandleHistory(x, y, historyId, prevoiusColor);
int index = x + (y * Width);
int col = color.ToArgb();
Bits[index] = col;
}
#endregion
#region UndoChanges()
/// <summary>
/// This method Undo Changes
/// </summary>
public void UndoChanges()
{
// If the History object exists
if ((this.HasHistory) && (this.History.HasChangedPixels))
{
// get the changed pixels
List<PixelInformation> pixels = this.History.ChangedPixels;
// Iterate the collection of PixelInformation objects
foreach (PixelInformation pixel in pixels)
{
// for debugging only
int alpha = pixel.Color.A;
// Restore this pixel
SetPixel(pixel.X, pixel.Y, pixel.Color);
}
// Remove the history
this.History = null;
}
}
#endregion
#endregion
#region Properties
#region Bitmap
/// <summary>
/// method [Enter Method Description]
/// </summary>
public Bitmap Bitmap { get; private set; }
#endregion
#region Bits
/// <summary>
/// method [Enter Method Description]
/// </summary>
public Int32[] Bits { get; private set; }
#endregion
#region BitsHandle
/// <summary>
/// This is a ptr to the garbage collector
/// </summary>
protected GCHandle BitsHandle { get; private set; }
#endregion
#region Disposed
/// <summary>
/// method [Enter Method Description]
/// </summary>
public bool Disposed { get; private set; }
#endregion
#region HasHistory
/// <summary>
/// This property returns true if this object has a 'History'.
/// </summary>
public bool HasHistory
{
get
{
// initial value
bool hasHistory = (this.History != null);
// return value
return hasHistory;
}
}
#endregion
#region Height
/// <summary>
/// method [Enter Method Description]
/// </summary>
public int Height { get; private set; }
#endregion
#region History
/// <summary>
/// This property gets or sets the value for 'History'.
/// </summary>
public History History
{
get { return history; }
set { history = value; }
}
#endregion
#region Width
/// <summary>
/// method [Enter Method Description]
/// </summary>
public int Width { get; private set; }
#endregion
#endregion
}
#endregion
}
这是完整的项目,因为很难发布完整的应用程序,我试图展示最相关的部分:
https://github.com/DataJuggler/TransparencyMaker
如果有人愿意观看,我的 YouTube 频道上有一个部分供 TransparencyMaker 视频使用:https ://youtu.be/7kfNKyr_oqg
第 2 版已发布,但仍在完善中。