1
\$\begingroup\$

I've started with developing BMP format reader in C#. This tool might read all data from binary BMP file and represents all data from it (from the header, the pixel data etc).

I have started and have done only first 14 bytes reading (header) and getting the pixel data from 24 bit BMP file. Of course there would be a lot of work, but I'd like to know if I'm developing this correctly.

Please, review my code:

using System;
using System.IO;
using System.Text;

class BMPDataInfo
{
    enum BMPHeaderTypes
    {
        BM, // Windows family
        BA, // OS/2 struct bitmap array
        CI, // OS/2 struct color icon
        CP, // OS/2 const color pointer
        IC, // OS/2 struct icon
        PT, // OS/2 pointer
        Invalid, // if two bytes from loaded file don't equal to any values of all BMP possible formats
    };

    byte[] fileData;
    Encoding fileEncoding;
    BMPFileInfo fileInfo;

    class BMPFileInfo
    {
        internal Encoding currentEncoding;
        internal BMPHeaderTypes currentBMPType;
        internal uint currentFileSize;
        internal int reservedFirst;
        internal int reservedSecond;
        internal int tablePixelStartAddress;
        internal int[] tablePixel;
    }

    public BMPDataInfo()
    {
        fileInfo = new BMPFileInfo();
    }

    internal byte[] ReadFile(string fileName)
    {
        FileStream fs = new FileStream(fileName, FileMode.Open);
        fileData = new byte[fs.Length];
        fs.Read(fileData, 0, fileData.Length);
        fs.Close();

        using (var fileStreamReader = new StreamReader(fileName, true))
        {
            fileEncoding = fileStreamReader.CurrentEncoding;
        }

        switch (fileEncoding.BodyName)
        {
            case "utf-8":
                fileInfo.currentEncoding = Encoding.UTF8;
                break;
            case "us-ascii":
                fileInfo.currentEncoding = Encoding.ASCII;
                break;
        }

        return fileData;
    }

    internal void BMPGetHeader()
    {
        #region Initializing the array size of 14 bytes for the BMP header

        byte[] headerBMP = new byte[14];

        for (int i = 0; i < headerBMP.Length; i++)
        {
            headerBMP[i] = fileData[i];
        }

        #endregion

        #region Getting first TWO bytes to check the type of BMP file format

        fileInfo.currentBMPType = BMPCheckHeaderType(ref headerBMP[0], ref headerBMP[1]);

        #endregion

        #region Getting file size from BMP header, NOT from the `Length` property of stream

        byte[] fileSizeHeaderInfo = new byte[4];

        // needed fix for the correct byte shift (data[i] = i << shiftValue)
        for (int i = 2, j = 0; j < fileSizeHeaderInfo.Length; i++, j++)
        {
            fileSizeHeaderInfo[j] = fileData[i];
        }

        fileInfo.currentFileSize = BMPGetFileSize(ref fileSizeHeaderInfo);

        #endregion

        #region Getting reserved fields from BMP header

        byte[] fileReservedInfo = new byte[4];

        for (int i = 6, j = 0; j < fileReservedInfo.Length; i++, j++)
        {
            fileReservedInfo[j] = fileData[i];
        }

        int[] reservedResult = BMPGetReservedFromHeader(ref fileReservedInfo);
        fileInfo.reservedFirst = reservedResult[0];
        fileInfo.reservedSecond = reservedResult[1];

        #endregion

        #region Getting the starting address, of the byte where the bitmap image data (pixel array) can be found

        byte[] filePixelArrayAddress = new byte[4];

        for (int i = 10, j = 0; j < filePixelArrayAddress.Length; i++, j++)
        {
            filePixelArrayAddress[j] = fileData[i];
        }

        fileInfo.tablePixelStartAddress = BMPGetStartAddress(ref filePixelArrayAddress);

        #endregion

        #region Reading the pixel table of the current BMP

        // needed to be refactored, because the test BMP file has 24 bit depth color, so: 1 pixel/3 bytes
        fileInfo.tablePixel = new int[fileData.Length - fileInfo.tablePixelStartAddress];
        int[] bufferTable = new int[3];

        for (int i = fileInfo.tablePixelStartAddress, j = 0, k = 0; i < fileInfo.tablePixel.Length; i++, k++)
        {
            if (k > 2)
            {
                int tmpValue = 0;

                for (int z = 0; z < k; z++)
                {
                    tmpValue += bufferTable[z];
                }

                fileInfo.tablePixel[j] = tmpValue;
                k = 0;
                j++;
                i++;
            }

            bufferTable[k] = fileData[i];
        }

        #endregion
    }

    BMPHeaderTypes BMPCheckHeaderType(ref byte firstValue, ref byte secondValue)
    {
        if (firstValue == 0x42 && secondValue == 0x4d)
        {
            return BMPHeaderTypes.BM;
        }

        return BMPHeaderTypes.Invalid;
    }

    uint BMPGetFileSize(ref byte[] headerPart)
    {
        uint fileSize = (uint)fileData[5] << 24
                        | (uint)fileData[4] << 16
                        | (uint)fileData[3] << 8
                        | (uint)fileData[2];

        return fileSize;
    }

    int[] BMPGetReservedFromHeader(ref byte[] headerPart)
    {
        int reservedFirst = headerPart[0] + headerPart[1];
        int reservedSecond = headerPart[2] + headerPart[3];

        int[] resultReserved = new int[2];
        resultReserved[0] = reservedFirst;
        resultReserved[1] = reservedSecond;

        return resultReserved;
    }

    int BMPGetStartAddress(ref byte[] headerPart)
    {
        int resultStartAddress = 0;

        for (int i = 0; i < headerPart.Length; i++)
        {
            resultStartAddress += headerPart[i];
        }

        return resultStartAddress;
    }
}

class App
{
    const string fileName = @"\1.bmp";

    static void Main()
    {
        try
        {
            BMPDataInfo objDataInfo = new BMPDataInfo();
            objDataInfo.ReadFile(fileName);
            objDataInfo.BMPGetHeader();
        }
        catch (Exception exc)
        {
            Console.WriteLine(exc);
        }
    }
}
\$\endgroup\$
7
\$\begingroup\$

Instead of marking pieces of functionality within your BMPGetHeader() method with regions. I would suggest turning each region into a private method. The code will be much easier to read and refactor once this is done.

\$\endgroup\$
  • \$\begingroup\$ there would be too much similar function with the foreach block, it the function BMPGetHeader() will consists of many similar small functions, wouldn't be? I suppose I could be wrong, please tell me the exact reasons to cut this function into few small, which will be to similar? \$\endgroup\$ – user1131997 Mar 23 '13 at 18:47
  • 4
    \$\begingroup\$ @OlegOrlov How is having several methods that are too similar any worse than having #regions in the same method that are similar? \$\endgroup\$ – svick Mar 23 '13 at 22:16
  • \$\begingroup\$ I think #regions are one of the best smell-indicators around (function to long, not-single responsible, ...) - remove them whereever you can \$\endgroup\$ – Carsten Jul 6 '14 at 7:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.