codice:
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
// ...
public static Bitmap LoadClippedBitmap(Stream fs, Rectangle viewport)
{
BinaryReader br = new BinaryReader (fs);
// Check magic number iniziale
if (br.ReadUInt16 () != 0x4d42)
throw new InvalidDataException ("Invalid BMP file");
fs.Seek (0x0a, SeekOrigin.Begin);
uint dataStart = br.ReadUInt32 ();
uint headerSize = br.ReadUInt32 ();
// Controlla se stiamo leggendo un BITMAPINFOHEADER/BITMAPV4HEADER/BITMAPV5HEADER
if (headerSize != 40 && headerSize != 108 && headerSize != 124)
throw new InvalidDataException ("Unsupported bitmap header version");
fs.Seek (0x12, SeekOrigin.Begin);
int width = br.ReadInt32 ();
int height = br.ReadInt32 ();
// Clippa il viewport se necessario
viewport.Intersect (new Rectangle (0, 0, width, height));
if (br.ReadInt16() != 1)
throw new InvalidDataException ("Invalid bit planes count");
short bpp = br.ReadInt16 ();
if (br.ReadUInt32 () != 0)
throw new InvalidDataException ("Unsupported compression method");
// Calcola la stride direttamente in bit
int stride = (width * bpp + 31) / 32 * 4;
PixelFormat fmt;
switch (bpp)
{
case 1:
fmt = PixelFormat.Format1bppIndexed;
break;
case 8:
fmt = PixelFormat.Format8bppIndexed;
break;
case 24:
fmt = PixelFormat.Format24bppRgb;
break;
default:
// In teoria si potrebbe avere anche 4, ma GDI+ non lo supporta e dovremmo
// fare upsampling al volo
throw new InvalidDataException ("Unsupported bit depth");
}
// Costruisce la bitmap da restituire
Bitmap ret = new Bitmap (viewport.Width, viewport.Height, fmt);
// Carica la palette
fs.Seek (0x2e, SeekOrigin.Begin);
uint paletteSize = br.ReadUInt32 ();
if (paletteSize != 0)
{
if (bpp > 8)
throw new InvalidDataException ("Error: palette unsupported for images with bpp > 8");
ColorPalette palette = ret.Palette;
fs.Seek (0x0e + headerSize, SeekOrigin.Begin);
for (int i=0; i<paletteSize; ++i)
palette.Entries [i] = Color.FromArgb(br.ReadInt32 ());
ret.Palette = palette;
}
// Blocca i pixel dell'immagine in memoria
BitmapData bd = ret.LockBits (new Rectangle (0, 0, viewport.Width, viewport.Height), ImageLockMode.ReadWrite, fmt);
int readLen = (viewport.Width * bpp + 7) / 8;
// Andiamo nel verso "sbagliato", in modo da andare in avanti nel file (aiuta la cache IO)
for (int y = viewport.Bottom-1; y>= viewport.Top; y--)
{
// Punto di partenza nel file
fs.Seek (dataStart + (height - 1 - y) * stride + (viewport.Left * bpp + 7) / 8, SeekOrigin.Begin);
// Indirizzo di destinazione in ret
IntPtr target = IntPtr.Add(bd.Scan0, (y-viewport.Top) * bd.Stride);
// Legge i dati e li copia nella bitmap target
byte[] data = br.ReadBytes (readLen);
Marshal.Copy (data, 0, target, data.Length);
}
// Sblocca i pixel
ret.UnlockBits (bd);
return ret;
}