Jana Andropov
Jana Andropov

Reputation: 139

Bitmap gets mangled when I converted from System.Drawing.Bitmap object to cv::Mat

I have a WPF application that takes a screen shot of the running Handbrake executable using a class called ScreenCapture that I copied from stack overflow.

public class ScreenCapture
{
    [DllImport("user32.dll")]
    static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn);

    //Region Flags - The return value specifies the type of the region that the function obtains. It can be one of the following values.
    const int ERROR = 0;
    const int NULLREGION = 1;
    const int SIMPLEREGION = 2;
    const int COMPLEXREGION = 3;

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);

    [DllImport("gdi32.dll")]
    static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left, Top, Right, Bottom;

        public RECT(int left, int top, int right, int bottom)
        {
            Left = left;
            Top = top;
            Right = right;
            Bottom = bottom;
        }

        public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }

        public int X
        {
            get { return Left; }
            set { Right -= (Left - value); Left = value; }
        }

        public int Y
        {
            get { return Top; }
            set { Bottom -= (Top - value); Top = value; }
        }

        public int Height
        {
            get { return Bottom - Top; }
            set { Bottom = value + Top; }
        }

        public int Width
        {
            get { return Right - Left; }
            set { Right = value + Left; }
        }

        public System.Drawing.Point Location
        {
            get { return new System.Drawing.Point(Left, Top); }
            set { X = value.X; Y = value.Y; }
        }

        public System.Drawing.Size Size
        {
            get { return new System.Drawing.Size(Width, Height); }
            set { Width = value.Width; Height = value.Height; }
        }

        public static implicit operator System.Drawing.Rectangle(RECT r)
        {
            return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
        }

        public static implicit operator RECT(System.Drawing.Rectangle r)
        {
            return new RECT(r);
        }

        public static bool operator ==(RECT r1, RECT r2)
        {
            return r1.Equals(r2);
        }

        public static bool operator !=(RECT r1, RECT r2)
        {
            return !r1.Equals(r2);
        }

        public bool Equals(RECT r)
        {
            return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
        }

        public override bool Equals(object obj)
        {
            if (obj is RECT)
                return Equals((RECT)obj);
            else if (obj is System.Drawing.Rectangle)
                return Equals(new RECT((System.Drawing.Rectangle)obj));
            return false;
        }

        public override int GetHashCode()
        {
            return ((System.Drawing.Rectangle)this).GetHashCode();
        }

        public override string ToString()
        {
            return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
        }
    }
    public Bitmap GetScreenshot(IntPtr ihandle)
    {
        IntPtr hwnd = ihandle;//handle here

        RECT rc;
        GetWindowRect(new HandleRef(null, hwnd), out rc);

        Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
        Graphics gfxBmp = Graphics.FromImage(bmp);
        IntPtr hdcBitmap;
        try
        {
            hdcBitmap = gfxBmp.GetHdc();
        }
        catch
        {
            return null;
        }
        bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
        gfxBmp.ReleaseHdc(hdcBitmap);
        if (!succeeded)
        {
            gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
        }
        IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
        GetWindowRgn(hwnd, hRgn);
        Region region = Region.FromHrgn(hRgn);//err here once
        if (!region.IsEmpty(gfxBmp))
        {
            gfxBmp.ExcludeClip(region);
            gfxBmp.Clear(Color.Transparent);
        }
        gfxBmp.Dispose();
        return bmp;
    }

    public void WriteBitmapToFile(string filename, Bitmap bitmap)
    {
        bitmap.Save(filename, ImageFormat.Bmp);
    }

So when the button click handler below is called a screenshot of the handbrake window is taken. I write it to the harddrive to make sure its ok: handbrake screen shot. I create an instance of a CLR class library ClassLibrary1::Class1 and call the method "DoSomething" passing it the System.Drawing.Bitmap object.

  private void button4_Click(object sender, RoutedEventArgs e)
  {
     string wName = "HandBrake";
     IntPtr hWnd = IntPtr.Zero;
     foreach (Process pList in Process.GetProcesses())
     {
        if (pList.MainWindowTitle.Contains(wName))
        {
           hWnd = pList.MainWindowHandle;

           var sc = new ScreenCapture();

           SetForegroundWindow(hWnd);
           var bitmap = sc.GetScreenshot(hWnd);

           sc.WriteBitmapToFile("handbrake.bmp", bitmap);

           Bitmap image1 = (Bitmap)System.Drawing.Image.FromFile("handbrake.bmp", true);

           ClassLibrary1.Class1 opencv = new ClassLibrary1.Class1();

           opencv.DoSomething(image1);
        }
     }
  }

Inside DoSomething I attempt to convert the System.Drawing.Bitmap to a OpenCV class cv::Mat. I call cv::imwrite to make sure the bitmap is still ok, unfortunately somethings gone wrong: mangled handbrake screenshot

void Class1::DoSomething(Bitmap ^mybitmap)
{
cv::Mat *imgOriginal;
// Lock the bitmap's bits.  
Rectangle rect = Rectangle(0, 0, mybitmap->Width, mybitmap->Height);
Imaging::BitmapData^ bmpData = mybitmap->LockBits(rect, Imaging::ImageLockMode::ReadWrite, mybitmap->PixelFormat);
try
{
  // Get the address of the first line.
  IntPtr ptr = bmpData->Scan0;

  // Declare an array to hold the bytes of the bitmap.
  // This code is specific to a bitmap with 24 bits per pixels.
  int bytes = Math::Abs(bmpData->Stride) * mybitmap->Height;
  array<Byte>^rgbValues = gcnew array<Byte>(bytes);

  // Copy the RGB values into the array.
  System::Runtime::InteropServices::Marshal::Copy(ptr, rgbValues, 0, bytes);

  imgOriginal = new cv::Mat(mybitmap->Height, mybitmap->Width, CV_8UC3, (void *)ptr, std::abs(bmpData->Stride));
  }
  finally { mybitmap->UnlockBits(bmpData); }//Remember to unlock!!!

  cv::imwrite("from_mat.bmp", *imgOriginal);
}

Can anybody spot my error?

Upvotes: 0

Views: 301

Answers (1)

David Yaw
David Yaw

Reputation: 27874

Since your image is stretched horizontally, I'm betting that you have the wrong pixel format selected. (It's not stretched vertically, nor skewed diagonally, so the stride is correct.) CV_8UC3 specifies 24 bits per pixel, but I think that your BMP file is using 32 bits per pixel.

Switch your pixel format to CV_8UC4, or better yet, read the number of bits per pixel from the image and select the correct CV format based on that.


Side note: Since you're doing sc.WriteBitmapToFile() followed by opencv.DoSomething(Image.FromFile(), the entire bit about how you're capturing the screenshot is irrelevant. You're reading the bitmap from a file; that's all that matters.

Upvotes: 2

Related Questions