System.ArgumentException HResult = 0x80070057 Message = Parameter is not valid. Source = System.Drawing. Why?

Viewed 805

Using c#, I have extracted a cursor resource from a PE file and removed the x-y hotspots and prepended the BITMAPFILEHEADER but when I try to convert to a BITMAP for display in a Picture Box as pbResImage.Image = Image.FromStream(bitmapStream.BaseStream); I receive an argument exception.

My code works for dozens of cursors and the cursor in question is extractable using other programs.

I believe the simplest approach is to examine the actual inputs to both by code (bitmapBytes) and to the stream passed to Image.FromStream (bitmapStream). I have tried and tried with no success.

I am asking your help in examining the inputs and identifying what is ACTUALLY INVALID about bitmapStream. Once I know what .NET is finding faulty, a solution will probably be at hand.

Here is the bitmapBytes array that I extracted:

0000- 28 00 00 00 20 00 00 00 40 00 00 00 01 00 04 00
0010- 00 00 00 00 80 02 00 00 00 00 00 00 00 00 00 00
0020- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 00
0030- 00 80 00 00 00 80 80 00 80 00 00 00 80 00 80 00
0040- 80 80 00 00 80 80 80 00 C0 c0 c0 00 00 00 FF 00
0050- 00 FF 00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00
0060- FF FF 00 00 FF FF FF 00 00 00 00 00 00 00 00 00
0070- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0080- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0090- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00a0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00b0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00c0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00d0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 88
00e0- 88 80 00 00 00 00 00 00 00 00 00 00 00 00 7F FF
00f0- FF F8 00 00 00 00 00 00 00 00 00 00 00 00 7F FF
0100- FF F8 00 00 00 00 00 00 00 00 00 00 00 07 FF FF
0110- FF FF 80 00 00 00 00 00 00 00 00 00 00 7F FF FF
0120- FF FF 80 00 00 00 00 00 00 00 00 00 07 FF FF FF
0130- FF FF F8 00 00 00 00 00 00 00 00 00 07 FF FF FF
0140- FF FF F8 00 00 00 00 00 00 00 00 00 7F FF FF FF
0150- FF FF F8 00 00 00 00 00 00 00 00 00 7F FF FF FF
0160- FF FF FF 80 00 00 00 00 00 00 00 07 FF FF FF FF
0170- FF FF FF 80 00 00 00 00 00 00 00 07 FF 88 FF FF
0180- FF FF FF 80 00 00 00 00 00 00 00 7F F8 00 FF FF
0190- FF FF FF 80 00 00 00 00 00 00 00 7F 80 07 FF FF
01a0- FF FF 7F 80 00 00 00 00 00 00 07 F8 00 07 FF 7F
01b0- F7 F8 0F 80 00 00 00 00 00 00 07 70 00 07 F8 0F
01c0- 80 F8 0F 80 00 00 00 00 00 00 00 00 00 07 F8 0F
01d0- 80 F8 07 70 00 00 00 00 00 00 00 00 00 07 F8 0F
01e0- 80 F8 00 00 00 00 00 00 00 00 00 00 00 07 F8 0F
01f0- 80 77 00 00 00 00 00 00 00 00 00 00 00 07 F8 07
0200- 70 00 00 00 00 00 00 00 00 00 00 00 00 07 F8 00
0210- 00 00 00 00 00 00 00 00 00 00 00 00 00 07 F8 00
0220- 00 00 00 00 00 00 00 00 00 00 00 00 00 07 F8 00
0230- 00 00 00 00 00 00 00 00 00 00 00 00 00 07 F8 00
0340- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 77 00
0250- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0260- 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF
0270- FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0280- FF FF FF FF FF F8 1F FF FF F0 0F FF FF F0 0F FF
0290- FF E0 03 FF FF c0 03 FF FF 80 01 FF FF 80 01 FF
02a0- FF 00 01 FF FF 00 00 FF FE 00 00 FF FE 00 00 FF
02b0- Fc 00 00 FF Fc 20 00 FF F8 60 00 FF F8 E0 00 FF
02c0- FF E0 01 FF FF E0 07 FF FF E0 0F FF FF E0 7F FF
02d0- FF E1 FF FF FF E1 FF FF FF E1 FF FF FF E1 FF FF
02e0- FF F3 FF FF FF FF FF FF

Here is the bitmapStream that is the "invalid" argument:

0000- 42 4D F6 02 00 00 00 00 00 00 76 00 00 00 28 00
0010- 00 00 20 00 00 00 40 00 00 00 01 00 04 00 00 00
0020- 00 00 80 02 00 00 00 00 00 00 00 00 00 00 00 00
0030- 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80
0040- 00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80
0050- 00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF
0060- 00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF
0070- 00 00 FF FF FF 00 00 00 00 00 00 00 00 00 00 00
0080- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0090- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00a0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00b0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00c0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00d0- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00e0- 00 00 00 00 00 00 00 00 00 00 00 00 08 88 88 80
00f0- 00 00 00 00 00 00 00 00 00 00 00 00 7F FF FF F8
0100- 00 00 00 00 00 00 00 00 00 00 00 00 7F FF FF F8
0110- 00 00 00 00 00 00 00 00 00 00 00 07 FF FF FF FF
0120- 80 00 00 00 00 00 00 00 00 00 00 7F FF FF FF FF
0130- 80 00 00 00 00 00 00 00 00 00 07 FF FF FF FF FF
0140- F8 00 00 00 00 00 00 00 00 00 07 FF FF FF FF FF
0150- F8 00 00 00 00 00 00 00 00 00 7F FF FF FF FF FF
0160- F8 00 00 00 00 00 00 00 00 00 7F FF FF FF FF FF
0170- FF 80 00 00 00 00 00 00 00 07 FF FF FF FF FF FF
0180- FF 80 00 00 00 00 00 00 00 07 FF 88 FF FF FF FF
0190- FF 80 00 00 00 00 00 00 00 7F F8 00 FF FF FF FF
01a0- FF 80 00 00 00 00 00 00 00 7F 80 07 FF FF FF FF
01b0- 7F 80 00 00 00 00 00 00 07 F8 00 07 FF 7F F7 F8
01c0- 0F 80 00 00 00 00 00 00 07 70 00 07 F8 0F 80 F8
01d0- 0F 80 00 00 00 00 00 00 00 00 00 07 F8 0F 80 F8
01e0- 07 70 00 00 00 00 00 00 00 00 00 07 F8 0F 80 F8
01f0- 00 00 00 00 00 00 00 00 00 00 00 07 F8 0F 80 77
0200- 00 00 00 00 00 00 00 00 00 00 00 07 F8 07 70 00
0210- 00 00 00 00 00 00 00 00 00 00 00 07 F8 00 00 00
0220- 00 00 00 00 00 00 00 00 00 00 00 07 F8 00 00 00
0230- 00 00 00 00 00 00 00 00 00 00 00 07 F8 00 00 00
0340- 00 00 00 00 00 00 00 00 00 00 00 07 F8 00 00 00
0250- 00 00 00 00 00 00 00 00 00 00 00 00 77 00 00 00
0260- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0270- 00 00 00 00 00 00 FF FF FF FF FF FF FF FF FF FF
0280- FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0290- FF FF FF F8 1F FF FF F0 0F FF FF F0 0F FF FF E0
02a0- 03 FF FF C0 03 FF FF 80 01 FF FF 80 01 FF FF 00
02b0- 01 FF FF 00 00 FF FE 00 00 FF FE 00 00 FF FC 00
02c0- 00 FF FC 20 00 FF F8 60 00 FF F8 E0 00 FF FF E0
02d0- 01 FF FF E0 07 FF FF E0 0F FF FF E0 7F FF FF E1
02e0- FF FF FF E1 FF FF FF E1 FF FF FF E1 FF FF FF F3
02f0- FF FF FF FF FF FF

Here is the relevant code:

    public void DisplayPECursor(TreeNode node)
    {
        Byte[] nodeBytes = (byte[])node.Tag;
        Byte[] bitmapBytes = new byte[nodeBytes.Length - 4];
        // Remove hotspot-x and hotspot-y values from the cursor resource data
        Buffer.BlockCopy(nodeBytes, 4, bitmapBytes, 0, nodeBytes.Count() - 4);

                   /////////////////////////////////// temp for debug
                   using (Stream file = File.OpenWrite(@"G:\___ProblemFiles\bitmapBytes.dat"))
                   {
                       file.Write(bitmapBytes, 0, bitmapBytes.Length);
                   }
                   ///////////////////////////////////

        (int sizeBITMAPINFOHEADER, int sizeColorTable) = GetBmHeaderInfo(bitmapBytes);


        int bitmapFileLen = bitmapBytes.Length + sizeBITMAPFILEHEADER;
        using (BinaryWriter bitmapStream = new BinaryWriter(new MemoryStream(bitmapFileLen)))
        {
            // Write the missing BITMAPFILEHEADER.
            const Int16 zero = 0;
            bitmapStream.Write(bmSignature);
            bitmapStream.Write(bitmapFileLen);
            bitmapStream.Write(zero);
            bitmapStream.Write(zero);
            bitmapStream.Write(sizeBITMAPFILEHEADER + sizeBITMAPINFOHEADER + sizeColorTable);
            bitmapStream.Seek(sizeBITMAPFILEHEADER, SeekOrigin.Begin);
            bitmapStream.Write(bitmapBytes, 0, bitmapBytes.Length);

                      /////////////////////////////////// temp for debug
                      //Save file for examination with hex editor
                      SaveStreamToFile(bitmapStream, @"G:\___ProblemFiles\My125.bmp");
                      ///////////////////////////////////

            pbResImage.Image = Image.FromStream(bitmapStream.BaseStream);
        }


            /////////////////////////////////////////////////////////////////// temp for debug
            //Internal method to save file for examination with hex editor
        void SaveStreamToFile(BinaryWriter streamName, string destFilename)
            {
                //The bmp file has been built in memory now save it to file
                  StreamReader reader = new StreamReader(streamName.BaseStream);
                int bufferLen = 4096;
                byte[] buffer = new byte[bufferLen];
                FileStream saveStream = new FileStream(destFilename, FileMode.Create, FileAccess.ReadWrite);
                streamName.Seek(0, SeekOrigin.Begin); // stream has been previously written so reposition to the beginning
                while (true)
                {
                    int read = streamName.BaseStream.Read(buffer, 0, bufferLen);

                    if (read > 0)
                    {
                        saveStream.Write(buffer, 0, read);
                    }
                    else
                        break;
                }
                saveStream.Close();
            }
            //////////////////////////////////////////////////////////////////////////
        return;
    }           
 _____________________________________________________________________________________________

    public (int sizeBITMAPINFOHEADER, int sizeColorTable) GetBmHeaderInfo(byte[] bitmapBytes)
    {
        //IntPtr offsetSizeInfoHeader = 
        //     Marshal.OffsetOf(typeof(NativeMethods.BITMAPINFOHEADER), "biSize");
        //IntPtr offsetBitCount = 
        //     Marshal.OffsetOf(typeof(NativeMethods.BITMAPINFOHEADER), "biBitCount");
        //IntPtr offsetClrUsed = 
        //     Marshal.OffsetOf(typeof(NativeMethods.BITMAPINFOHEADER), "biClrUsed");
        //IntPtr offsetCompression = 
        //     Marshal.OffsetOf(typeof(NativeMethods.BITMAPINFOHEADER), "biCompression");
        //int sizeBITMAPFILEHEADER = 
        //     System.Runtime.InteropServices.Marshal.SizeOf(typeof(NativeMethods.BITMAPFILEHEADER));
        //int sizeRGBQUAD = 
        //     System.Runtime.InteropServices.Marshal.SizeOf(typeof(NativeMethods.RGBQUAD));
        //UInt16 bmSignature;
        // the above variables were refactored to be class level; left here for now for clarity
        // for the Stackoverflow community.

        var headerSize = new byte[2];
        var bitCount = new byte[2];
        var compression = new byte[4];
        var clrUsed = new byte[4];
        Array.Copy(bitmapBytes, (int)offsetSizeInfoHeader, headerSize, 0, 2);
        Array.Copy(bitmapBytes, (int)offsetBitCount, bitCount, 0, 2);
        Array.Copy(bitmapBytes, (int)offsetCompression, compression, 0, 4);
        Array.Copy(bitmapBytes, (int)offsetClrUsed, clrUsed, 0, 4);
        int sizeBITMAPINFOHEADER = BitConverter.ToInt16(headerSize, 0);
        int count = BitConverter.ToInt16(bitCount, 0);
        int compress = BitConverter.ToInt32(compression, 0);
        int used = BitConverter.ToInt32(clrUsed, 0);
        if (used == 0) used = (int)Math.Pow(2, count);  // Assume the maximum if used = 0
        // Determine the size of the color table
        int sizeColorTable = 0;
        if (compress == (int)NativeMethods.biCompression.BI_BITFIELDS)
        {
            sizeColorTable = 12;
        }
        else if (compress == (int)NativeMethods.biCompression.BI_ALPHABITFIELDS)
        {
            sizeColorTable = 16;
        }
        else if (count < 16 && used > 0)
        {
            sizeColorTable = sizeRGBQUAD * used;
        }
        return (sizeBITMAPINFOHEADER, sizeColorTable);
    }              
1 Answers

There is a special type of DIB image used for indexed cursors and icons that has a double height set in the header, but actually contains a 1-bit AND mask behind it that is used to indicate which pixels are transparent.

If you analyse the header, the data you have there seems to be a 4 bit, 32x64 DIB image, but it's actually the combination of a 4-bit 32x32 image, and then a 1-bit 32x32 mask.

This can be detected exactly by considering it like that; if the height is divisible by 2, take half the height, then combine the size expected for an image of that half-height at the given pixel format, plus the size expected for an image of that half-height at 1 bit per pixel (keeping in mind that the stride is always a multiple of 4 bytes). If this matches the size of the image data you got, you are dealing with the transparent icon format.

This size value to check is the biSizeImage value in the BITMAPINFOHEADER, which is a field your current code doesn't seem to read at all. It's a 32-bit integer that comes right after the compression type field.

Boolean isIcon = false;
Int32 maskSize = 0;
Int32 stride = GetClassicStride(biWidth, biBitCount);
Int32 actualHeight = biHeight;
if (biHeight % 2 == 0)
{
    Int32 halfHeight = biHeight / 2;
    Int32 maskStride = GetClassicStride(biWidth, 1);
    Int32 maskSizeCheck = maskStride * halfHeight;
    if (biSizeImage = stride * halfHeight + maskSizeCheck)
    {
        isIcon = true;
        actualHeight = halfHeight;
        maskSize = maskSizeCheck;
    }
}

Classic stride formula:

public static Int32 GetClassicStride(Int32 width, Int32 bitsLength)
{
    return (((((bitsLength * width) + 7) / 8) + 3) / 4) * 4;
}

The resulting image:

Resulting image

This format only works for icon files, not for classic bitmaps, which is why your bmp file header doesn't do the job. So either look into the specific icon/cursor file format instead, or convert it to a 32-bit image and apply the mask on the image data manually (as I did to produce the image above).

Technically, this format can also be used for 32-bit icons, by having a 24bpp base image plus an 8bpp alpha mask. In this case, the height in the header will also be doubled. But I haven't actually encountered that format so far.

The Wikipedia article on Windows ICO:

https://en.wikipedia.org/wiki/ICO_(file_format)

The specs of the ico format:

https://web.archive.org/web/20160531004250/https://msdn.microsoft.com/en-us/library/ms997538.aspx

Related