3

I've a very large bitmap that I'm trying to view using a C# application .

The main issue here I can't load it directly into memory , thus I tried to use memory mapped view library to load it using guidance from Reference One, Reference Two , Reference Three , Reference Four and Reference Five .

What I reached till now is the following:- Reading the bitmap in parts as I need ( For example I read the first 200 row). Create another bitmap from the rows I read and display it.

Porblem:- The reconstructed bitmap image part loses the color information and is displayed up side down.

Example:- [Note I use low size image here and try to display part of it for testing purpose]

The Real Image:- enter image description here

The output which should be (select first 200 row and reconstruct a smaller bitmap and display it):- enter image description here As you can see the reconstructed image is colorless and upside down .

Now the code part:- class BMPMMF which is responsible for the whole process

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;

namespace BMPViewer
{
    class BMPMMF
    {
        /// <summary>
        /// It opens the image using memory mapped view and read the needed 
        /// parts, then call CreateBM to create a partially bitmap
        /// </summary>
        /// <param name="bmpFilename">Path to the physical bitmap</param>
        /// <returns></returns>
        public Bitmap readPartOfImage(string bmpFilename)
        {
            var headers = ReadHeaders(bmpFilename);
            var mmf = MemoryMappedFile.CreateFromFile(bmpFilename, FileMode.Open);
            int rowSize = headers.Item2.RowSize;    // number of byes in a row

           // Dictionary<ColorObject, int> rowColors = new Dictionary<ColorObject, int>();

            int colorSize = Marshal.SizeOf(typeof(MyColor));
            int width = rowSize / colorSize;//(headers.Item1.DataOffset+ rowSize) / colorSize;
            int height = 200;
            ColorObject cObj;
            MyColor outObj;
            ColorObject[][] rowColors = new ColorObject[height][];
            // Read the view image and save row by row pixel
            for (int j = 0; j < height; j++)
            {
                rowColors[j] = new ColorObject[width];
                using (var view = mmf.CreateViewAccessor(headers.Item1.DataOffset + rowSize * j, rowSize, MemoryMappedFileAccess.Read))
                {
                    for (long i = 0; i < rowSize; i += colorSize)
                    {

                        view.Read(i, out outObj);
                        cObj = new ColorObject(outObj);
                        rowColors[j][i / colorSize] = cObj;


                    }
                }
            }
            return CreateBM( rowColors );

        }
        /// <summary>
        /// Used to create a bitmap from provieded bytes 
        /// </summary>
        /// <param name="rowColors">Contains bytes of bitmap</param>
        /// <returns></returns>
        private Bitmap CreateBM(ColorObject[][] rowColors )
        {
            int width = rowColors[0].Count();
            int height = rowColors.Count();
            //int width = rowColors.Values.Where(o => o == 0).Count();
            Bitmap bitm = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            // new Bitmap(imgdat.GetUpperBound(1) + 1, imgdat.GetUpperBound(0) + 1, PixelFormat.Format24bppRgb);
            BitmapData bitmapdat = bitm.LockBits(new Rectangle(0, 0, bitm.Width, bitm.Height), ImageLockMode.ReadWrite, bitm.PixelFormat);
            int stride = bitmapdat.Stride;
            byte[] bytes = new byte[stride * bitm.Height];

            for (int r = 0; r < bitm.Height; r++)
            {

                for (int c = 0; c < bitm.Width; c++)
                {
                    ColorObject color = rowColors[r][c];
                    bytes[(r * stride) + c * 3] = color.Blue;
                    bytes[(r * stride) + c * 3 + 1] = color.Green;
                    bytes[(r * stride) + c * 3 + 2] = color.Red;

                }
            }


            System.IntPtr scan0 = bitmapdat.Scan0;
            Marshal.Copy(bytes, 0, scan0, stride * bitm.Height);
            bitm.UnlockBits(bitmapdat);

            return bitm;
        }

        /// <summary>
        /// Returns a tuple that contains necessary information about bitmap header 
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        private Tuple<BmpHeader, DibHeader> ReadHeaders(string filename)
        {
            var bmpHeader = new BmpHeader();
            var dibHeader = new DibHeader();
            using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                using (var br = new BinaryReader(fs))
                {
                    bmpHeader.MagicNumber = br.ReadInt16();
                    bmpHeader.Filesize = br.ReadInt32();
                    bmpHeader.Reserved1 = br.ReadInt16();
                    bmpHeader.Reserved2 = br.ReadInt16();
                    bmpHeader.DataOffset = br.ReadInt32();

                    dibHeader.HeaderSize = br.ReadInt32();
                    if (dibHeader.HeaderSize != 40)
                    {
                        throw new ApplicationException("Only Windows V3 format supported.");
                    }
                    dibHeader.Width = br.ReadInt32();
                    dibHeader.Height = br.ReadInt32();
                    dibHeader.ColorPlanes = br.ReadInt16();
                    dibHeader.Bpp = br.ReadInt16();
                    dibHeader.CompressionMethod = br.ReadInt32();
                    dibHeader.ImageDataSize = br.ReadInt32();
                    dibHeader.HorizontalResolution = br.ReadInt32();
                    dibHeader.VerticalResolution = br.ReadInt32();
                    dibHeader.NumberOfColors = br.ReadInt32();
                    dibHeader.NumberImportantColors = br.ReadInt32();
                }
            }

            return Tuple.Create(bmpHeader, dibHeader);
        }
    }

    public struct MyColor
    {
        public byte Red;
        public byte Green;
        public byte Blue;
        //public byte Alpha;
    }
    public class ColorObject
    {
        public ColorObject(MyColor c)
        {
            this.Red = c.Red;
            this.Green = c.Green;
            this.Blue = c.Blue;
           // this.Alpha = c.Alpha;
        }
        public byte Red;
        public byte Green;
        public byte Blue;
       // public byte Alpha;
    }

    public class BmpHeader
    {
        public short MagicNumber { get; set; }
        public int Filesize { get; set; }
        public short Reserved1 { get; set; }
        public short Reserved2 { get; set; }
        public int DataOffset { get; set; }
    }

    public class DibHeader
    {
        public int HeaderSize { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public short ColorPlanes { get; set; }
        public short Bpp { get; set; }
        public int CompressionMethod { get; set; }
        public int ImageDataSize { get; set; }
        public int HorizontalResolution { get; set; }
        public int VerticalResolution { get; set; }
        public int NumberOfColors { get; set; }
        public int NumberImportantColors { get; set; }
        public int RowSize
        {
            get
            {
                return 4 * ((Bpp * Width) / 32);
            }
        }
    }
}

This is how to use it : -

   Bitmap bmpImage = bmp.readPartOfImage(filePath); // path to bitmap
   pictBoxBMP.Image = bmpImage; // set the picture box image to the new Partially created bitmap 

Solution I seek:- Just display the partial created bitmap correctly , my guess that there's a problem in bitmap reconstruction or in bitmap reading using memory mapped view .

Update#1 : After applying @TaW solution I got the colors displayed , even they are a little bit different from the original color but it's accepted . enter image description here

Community
  • 1
  • 1
xsari3x
  • 442
  • 2
  • 12
  • 36
  • 1
    Based on image you are ignoring palette, padding bytes on every scan line and order of lines... Check out bitmap format specification for good understanding of all fields (Wikipedia have good starting article - http://en.wikipedia.org/wiki/BMP_file_format) – Alexei Levenkov Feb 03 '15 at 23:20
  • @AlexeiLevenkov I already check it , the format of Image I read is Format24bppRgb ( 3 bytes one for red , one for blue and one for green) . and the image i reconstruct is Format24bppRgb. – xsari3x Feb 03 '15 at 23:22
  • 2
    It looks like you may not be accounting for the *stride* of rows. Most image formats begin each row at some alignment, padding the previous one. – Cory Nelson Feb 03 '15 at 23:23
  • @CoryNelson explain more – xsari3x Feb 03 '15 at 23:28
  • 2
    Despite the fact that you said "I already check it" I'd recommend reading it more carefully - I.e. as @CoryNelson suggests you are not taking into account "The pixel format is defined by the DIB Header or Extra bit masks. Each row in the Pixel Array *is padded to a multiple of 4 bytes in size*" (from Wiki) – Alexei Levenkov Feb 03 '15 at 23:34
  • I cant get it ... Mmmmm , how should this affect the code ? – xsari3x Feb 03 '15 at 23:47

2 Answers2

3

For the inversion part, you have to check the order (bottom-up or top-down) of your input bitmap data. You can do this by checking the sign of the DibHeader Height property. Usually, negative height indicates a top-down layout. https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376%28v=vs.85%29.aspx

Then you know if you have to invert your data, copying the first line of your input bitmap to the last of the destination bitmap.

        for (int r = 0; r < bitm.Height; r++)
        {

            for (int c = 0; c < bitm.Width; c++)
            {
                ColorObject color = rowColors[r][c];
                bytes[( (bitm.Height - r - 1) * stride) + c * 3] = color.Blue;
                bytes[( (bitm.Height - r - 1) * stride) + c * 3 + 1] = color.Green;
                bytes[( (bitm.Height - r - 1) * stride) + c * 3 + 2] = color.Red;

            }
        }
tumasgiu
  • 325
  • 3
  • 11
  • It inveretd the display correctly and one more modifications is changing the Blue in place of Red made the colors appears correctly. – xsari3x Feb 04 '15 at 16:39
  • Now you both helped in the solution , who should I mark as correct ? – – xsari3x Feb 04 '15 at 16:43
  • I didn't know that one could edit someone else answer. If I had knew, I would have just completed TaW answer. So I think that the correct answer should be his ! – tumasgiu Feb 04 '15 at 17:43
  • Yes, you can, although at your current rep level the edits will undergo a review process. At 2000 the edits to questions and answers will take effect immediately. Editing is encouraged, not only when you want to add to an answer but also when you see errors in spelling and formatting. For a while (I forgot, I think it was until you have gained 200 rep) you even get 2rep for every approved edit you make. – TaW Feb 04 '15 at 18:31
2

I believe you don't get the Stride right. Judging from your Color structure/class, you use 24Bpp..

For 24Bpp the padding to add would be Width % 4 so in the RowSize getter change

return 4 * ((Bpp * Width) / 32);

to

return (Bpp / 8 * Width) + Width % 4;

For the general case you can get the padding for the stride as

Padding = Width % (4 * (4 - (Bpp / 8) ); 

The order of the rows is upside down (i.e. bottom up) by definition but you create it top down! So in the CreateBM change

for (int r = 0; r < bitm.Height; r++)

to

for (int r = bitm.Height - 1 ; r > 0; r--)

As for the colors: Try fixing these things first, then report back with the results, ok?

TaW
  • 53,122
  • 8
  • 69
  • 111
  • changing the for loop didn't change the upside down mechanism , after changing the padding the colors are correct ! – xsari3x Feb 04 '15 at 11:17
  • Hm, the colors look good now but are anything but 'correct' imo! I wonder if the upside down and the color shift are related.. - Is the updated image from your code or is the on the disk? – TaW Feb 04 '15 at 12:55
  • Also: please try using a different image! The repeating numbers in every row make it easy to mistake one row for some other row! Getting the color channels wrong once and picking the wrong lines could well result in the image you posted.. – TaW Feb 04 '15 at 13:06
  • Did you change the row order in the creation routine or in the reading or both? (Changing both will not help ;-) Also using tumasgiu's way to invert the order look good to as well; but again: Inverting twice will cancel the changes.. – TaW Feb 04 '15 at 13:28
  • this is the image generated from the program [Update #1] , I first applied the stride part and got colors fixed , tried the inverse and didn't reverse it – xsari3x Feb 04 '15 at 16:26
  • I applied @tumasgiu solution and it inverted the display correctly , later i changed the "red " byte in place of "blue" byte and color issue solved correctly – xsari3x Feb 04 '15 at 16:41
  • Now you both helped in the solution , who should I mark as correct ? – xsari3x Feb 04 '15 at 16:42
  • while I have solved more issues in my answer and my comments tumasgiu needs them more than I do :-) so you can mark his. Your edited question has made it clear that getting the stride right was one of the key things.. If you still want to mark mine I will upvote him. – TaW Feb 04 '15 at 16:58