问题
I have a Bitmap object of a hand-written survey (see survey image below) which contains various checkboxes. I am using an algorithm to compare a Bitmap of a blank, unmarked checkbox against a Bitmap of the same checkbox (which may or may not be marked), in order to determine if the checkbox has been marked or not.
Would it be possible to break the main survey Bitmap into smaller Bitmap objects? For example, with question 14 below I would like to make smaller Bitmap objects out of the red squares given the startX, endX, startY and endY locations of each checkbox.
回答1:
I have an open source program called Transparency Maker that I am working on as we speak that could help you do this, or at least give you some guides on how to get started.
My project reads in a Bitmap (.png or .jpg) and creates a Pixel Database. Actually it is a List of PixelInformation objects.
LoadPixelDatabase Method
My Windows Forms app has a PictureBox called Canvas for the background image. You could easily pass in an image as a parameter instead.
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);
}
}
}
The way my app could help you is, once the PixelDatabase is loaded, you can perform LinqQueries such as:
// 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();
(I know the above could be written in 1 line, it is hard to post here).
Once you have your pixels, you can create a new image:
Image image = new Bitmap(width, height);
Create another DirectBitmap for the new image, and then copy the pixels from the query above into your new image and save.
// Save the bitmap
bitmap.Save(fileName);
PixelDatabase.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
}
PixelInformation.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
This class here is called DirectBitmap, I didn't write it but I wish I knew who the author was to give them credit as it sped up my app by quite a bit.
#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
}
Here is the full Project, as it is hard to post a complete application, I tried to show the most relevant parts:
https://github.com/DataJuggler/TransparencyMaker
I have a section on my YouTube channel for TransparencyMaker videos if anyone would care to watch: https://youtu.be/7kfNKyr_oqg
Version 2 is released, but still being polished.
来源:https://stackoverflow.com/questions/60384432/making-smaller-bitmaps-from-a-bitmap-object