问题
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:-
The output which should be (select first 200 row and reconstruct a smaller bitmap and display it):-
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 .
回答1:
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?
回答2:
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;
}
}
来源:https://stackoverflow.com/questions/28310592/viewing-a-large-bitmap-image-using-memory-mapped-view