0

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.

enter image description here

darego101
  • 319
  • 2
  • 15

1 Answers1

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.