/// <summary> /// Reads the <see cref="BmpInfoHeader"/> from the stream. /// </summary> private void ReadInfoHeader() { #if NETCOREAPP2_1 Span <byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; #else byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize]; #endif this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); // read the header size int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); if (headerSize < BmpInfoHeader.CoreSize) { throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } int skipAmount = 0; if (headerSize > BmpInfoHeader.MaxHeaderSize) { skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize; headerSize = BmpInfoHeader.MaxHeaderSize; } // read the rest of the header this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); if (headerSize == BmpInfoHeader.CoreSize) { // 12 bytes this.infoHeader = BmpInfoHeader.ParseCore(buffer); } else if (headerSize >= BmpInfoHeader.Size) { // >= 40 bytes this.infoHeader = BmpInfoHeader.Parse(buffer); } else { throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } // Resolution is stored in PPM. var meta = new ImageMetaData(); meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter; if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) { meta.HorizontalResolution = this.infoHeader.XPelsPerMeter; meta.VerticalResolution = this.infoHeader.YPelsPerMeter; } else { // Convert default metadata values to PPM. meta.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultHorizontalResolution)); meta.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetaData.DefaultVerticalResolution)); } this.metaData = meta; // skip the remaining header because we can't read those parts this.stream.Skip(skipAmount); }
/// <summary> /// Writes the bitmap information to the binary stream. /// </summary> /// <param name="writer"> /// The <see cref="EndianBinaryWriter"/> containing the stream to write to. /// </param> /// <param name="infoHeader"> /// The <see cref="BmpFileHeader"/> containing the detailed information about the image. /// </param> private void WriteInfo(EndianBinaryWriter writer, BmpInfoHeader infoHeader) { writer.Write(infoHeader.HeaderSize); writer.Write(infoHeader.Width); writer.Write(infoHeader.Height); writer.Write(infoHeader.Planes); writer.Write(infoHeader.BitsPerPixel); writer.Write((int)infoHeader.Compression); writer.Write(infoHeader.ImageSize); writer.Write(infoHeader.XPelsPerMeter); writer.Write(infoHeader.YPelsPerMeter); writer.Write(infoHeader.ClrUsed); writer.Write(infoHeader.ClrImportant); }
/// <summary> /// Reads the <see cref="BmpInfoHeader"/> from the stream. /// </summary> private void ReadInfoHeader() { byte[] data = new byte[BmpInfoHeader.MaxHeaderSize]; // read header size this.currentStream.Read(data, 0, BmpInfoHeader.HeaderSizeSize); int headerSize = BitConverter.ToInt32(data, 0); if (headerSize < BmpInfoHeader.BitmapCoreHeaderSize) { throw new NotSupportedException($"This kind of bitmap files (header size $headerSize) is not supported."); } int skipAmmount = 0; if (headerSize > BmpInfoHeader.MaxHeaderSize) { skipAmmount = headerSize - BmpInfoHeader.MaxHeaderSize; headerSize = BmpInfoHeader.MaxHeaderSize; } // read the rest of the header this.currentStream.Read(data, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); switch (headerSize) { case BmpInfoHeader.BitmapCoreHeaderSize: this.infoHeader = this.ParseBitmapCoreHeader(data); break; case BmpInfoHeader.BitmapInfoHeaderSize: this.infoHeader = this.ParseBitmapInfoHeader(data); break; default: if (headerSize > BmpInfoHeader.BitmapInfoHeaderSize) { this.infoHeader = this.ParseBitmapInfoHeader(data); break; } else { throw new NotSupportedException($"This kind of bitmap files (header size $headerSize) is not supported."); } } // skip the remaining header because we can't read those parts this.currentStream.Skip(skipAmmount); }
/// <summary> /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are /// very similar to the Bitmap v3 header. The other 24 bytes are ignored, but they do not hold any /// useful information for decoding the image. /// </summary> /// <param name="data">The data to parse.</param> /// <returns>The parsed header.</returns> /// <seealso href="https://www.fileformat.info/format/os2bmp/egff.htm"/> public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan <byte> data) { var infoHeader = new BmpInfoHeader( headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)), width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); int compression = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)); // The compression value in OS/2 bitmap has a different meaning than in windows bitmaps. // Map the OS/2 value to the windows values. switch (compression) { case 0: infoHeader.Compression = BmpCompression.RGB; break; case 1: infoHeader.Compression = BmpCompression.RLE8; break; case 2: infoHeader.Compression = BmpCompression.RLE4; break; case 4: infoHeader.Compression = BmpCompression.RLE24; break; default: // Compression type 3 (1DHuffman) is not supported. BmpThrowHelper.ThrowImageFormatException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); break; } infoHeader.ImageSize = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)); infoHeader.XPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)); infoHeader.YPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)); infoHeader.ClrUsed = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)); infoHeader.ClrImportant = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)); // The following 24 bytes of the header are omitted. return(infoHeader); }
/// <summary> /// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream) where TPixel : struct, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); // Cast to int will get the bytes per pixel short bpp = (short)(8 * (int)this.bitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel); var infoHeader = new BmpInfoHeader( headerSize: BmpInfoHeader.Size, height: image.Height, width: image.Width, bitsPerPixel: bpp, planes: 1, imageSize: image.Height * bytesPerLine, clrUsed: 0, clrImportant: 0); var fileHeader = new BmpFileHeader( type: 19778, // BM offset: 54, reserved: 0, fileSize: 54 + infoHeader.ImageSize); #if NETCOREAPP2_1 Span <byte> buffer = stackalloc byte[40]; #else byte[] buffer = new byte[40]; #endif fileHeader.WriteTo(buffer); stream.Write(buffer, 0, BmpFileHeader.Size); infoHeader.WriteTo(buffer); stream.Write(buffer, 0, 40); this.WriteImage(stream, image.Frames.RootFrame); stream.Flush(); }
/// <summary> /// Reads the <see cref="BmpInfoHeader"/> from the stream. /// </summary> private void ReadInfoHeader() { #if NETCOREAPP2_1 Span <byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; #else byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize]; #endif this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); // read the header size int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); if (headerSize < BmpInfoHeader.CoreSize) { throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } int skipAmount = 0; if (headerSize > BmpInfoHeader.MaxHeaderSize) { skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize; headerSize = BmpInfoHeader.MaxHeaderSize; } // read the rest of the header this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); if (headerSize == BmpInfoHeader.CoreSize) { // 12 bytes this.infoHeader = BmpInfoHeader.ParseCore(buffer); } else if (headerSize >= BmpInfoHeader.Size) { // >= 40 bytes this.infoHeader = BmpInfoHeader.Parse(buffer); } else { throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } // skip the remaining header because we can't read those parts this.stream.Skip(skipAmount); }
/// <summary> /// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream) where TPixel : struct, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); // Cast to int will get the bytes per pixel short bpp = (short)(8 * (int)this.bitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel); // Do not use IDisposable pattern here as we want to preserve the stream. var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); var infoHeader = new BmpInfoHeader { HeaderSize = sizeof(uint), Height = image.Height, Width = image.Width, BitsPerPixel = (ushort)bpp, Planes = 1, ImageSize = (uint)(image.Height * bytesPerLine), ClrUsed = 0, ClrImportant = 0 }; uint offset = (uint)(BmpFileHeader.Size + infoHeader.HeaderSize); var fileHeader = new BmpFileHeader { Type = 0x4D42, // BM FileSize = offset + (uint)infoHeader.ImageSize, Reserved1 = 0, Reserved2 = 0, Offset = offset }; WriteHeader(writer, fileHeader); this.WriteInfo(writer, infoHeader); this.WriteImage(writer, image.Frames.RootFrame); writer.Flush(); }
/// <summary> /// Reads the <see cref="BmpInfoHeader"/> from the stream. /// </summary> private void ReadInfoHeader() { byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize]; // read header size this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); int headerSize = BitConverter.ToInt32(buffer, 0); if (headerSize < BmpInfoHeader.CoreSize) { throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } int skipAmount = 0; if (headerSize > BmpInfoHeader.MaxHeaderSize) { skipAmount = headerSize - BmpInfoHeader.MaxHeaderSize; headerSize = BmpInfoHeader.MaxHeaderSize; } // read the rest of the header this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); if (headerSize == BmpInfoHeader.CoreSize) { // 12 bytes this.infoHeader = BmpInfoHeader.ParseCore(buffer); } else if (headerSize >= BmpInfoHeader.Size) { // >= 40 bytes this.infoHeader = BmpInfoHeader.Parse(buffer.AsSpan(0, 40)); } else { throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}."); } // skip the remaining header because we can't read those parts this.stream.Skip(skipAmount); }
/// <summary> /// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; short bpp = (short)this.bitsPerPixel; int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); // Set Resolution. int hResolution = 0; int vResolution = 0; if (metadata.ResolutionUnits != PixelResolutionUnit.AspectRatio) { if (metadata.HorizontalResolution > 0 && metadata.VerticalResolution > 0) { switch (metadata.ResolutionUnits) { case PixelResolutionUnit.PixelsPerInch: hResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.HorizontalResolution)); vResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.VerticalResolution)); break; case PixelResolutionUnit.PixelsPerCentimeter: hResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.HorizontalResolution)); vResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.VerticalResolution)); break; case PixelResolutionUnit.PixelsPerMeter: hResolution = (int)Math.Round(metadata.HorizontalResolution); vResolution = (int)Math.Round(metadata.VerticalResolution); break; } } } int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3; var infoHeader = new BmpInfoHeader( headerSize: infoHeaderSize, height: image.Height, width: image.Width, bitsPerPixel: bpp, planes: 1, imageSize: image.Height * bytesPerLine, clrUsed: 0, clrImportant: 0, xPelsPerMeter: hResolution, yPelsPerMeter: vResolution); if (this.writeV4Header && this.bitsPerPixel == BmpBitsPerPixel.Pixel32) { infoHeader.AlphaMask = Rgba32AlphaMask; infoHeader.RedMask = Rgba32RedMask; infoHeader.GreenMask = Rgba32GreenMask; infoHeader.BlueMask = Rgba32BlueMask; infoHeader.Compression = BmpCompression.BitFields; } int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0; var fileHeader = new BmpFileHeader( type: BmpConstants.TypeMarkers.Bitmap, fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize, reserved: 0, offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); Span <byte> buffer = stackalloc byte[infoHeaderSize]; fileHeader.WriteTo(buffer); stream.Write(buffer, 0, BmpFileHeader.Size); if (this.writeV4Header) { infoHeader.WriteV4Header(buffer); } else { infoHeader.WriteV3Header(buffer); } stream.Write(buffer, 0, infoHeaderSize); this.WriteImage(stream, image.Frames.RootFrame); stream.Flush(); }
/// <summary> /// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream) where TPixel : struct, IPixel <TPixel> { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); // Cast to int will get the bytes per pixel short bpp = (short)(8 * (int)this.bitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel); // Set Resolution. ImageMetaData meta = image.MetaData; int hResolution = 0; int vResolution = 0; if (meta.ResolutionUnits != PixelResolutionUnit.AspectRatio) { if (meta.HorizontalResolution > 0 && meta.VerticalResolution > 0) { switch (meta.ResolutionUnits) { case PixelResolutionUnit.PixelsPerInch: hResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); vResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); break; case PixelResolutionUnit.PixelsPerCentimeter: hResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); vResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); break; case PixelResolutionUnit.PixelsPerMeter: hResolution = (int)Math.Round(meta.HorizontalResolution); vResolution = (int)Math.Round(meta.VerticalResolution); break; } } } var infoHeader = new BmpInfoHeader( headerSize: BmpInfoHeader.Size, height: image.Height, width: image.Width, bitsPerPixel: bpp, planes: 1, imageSize: image.Height * bytesPerLine, clrUsed: 0, clrImportant: 0, xPelsPerMeter: hResolution, yPelsPerMeter: vResolution); var fileHeader = new BmpFileHeader( type: 19778, // BM offset: 54, reserved: 0, fileSize: 54 + infoHeader.ImageSize); #if NETCOREAPP2_1 Span <byte> buffer = stackalloc byte[40]; #else byte[] buffer = new byte[40]; #endif fileHeader.WriteTo(buffer); stream.Write(buffer, 0, BmpFileHeader.Size); infoHeader.WriteTo(buffer); stream.Write(buffer, 0, 40); this.WriteImage(stream, image.Frames.RootFrame); stream.Flush(); }
/// <summary> /// Parses the <see cref="BmpInfoHeader"/> from the stream, assuming it uses the <c>WinInfoHeaderV3</c>, <c>WinInfoHeaderV4</c> or <see cref="WinInfoHeaderV5"/> format. /// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx">See this MSDN link for more information.</seealso> /// </summary> /// <param name="data">Header bytes read from the stream</param> /// <param name="headerSize">Header maximum bytes to read from <c>data</c></param> /// <returns>Parsed header</returns> private BmpInfoHeader ParseBitmapInfoHeader(byte[] data, int headerSize = (int)BmpNativeStructuresSizes.BITMAPINFOHEADER) { // Invalid header size if ((headerSize < (int)BmpNativeStructuresSizes.BITMAPINFOHEADER) || (headerSize > (int)BmpNativeStructuresSizes.BITMAPV5HEADER)) { throw new NotSupportedException($"This kind of IBM OS/2 bitmap files (header size $headerSize) is not supported."); } var bmpInfo = new BmpInfoHeader { // Mark the header as a Microsoft Windows v3 or above informatiom header for the DIB IsOS2InfoHeaderV2 = false, HeaderSize = BitConverter.ToUInt32(data, 0), Width = BitConverter.ToInt32(data, 4), Height = BitConverter.ToInt32(data, 8), Planes = BitConverter.ToUInt16(data, 12), BitsPerPixel = BitConverter.ToUInt16(data, 14), Compression = (BmpCompression)BitConverter.ToUInt32(data, 16), ImageSize = BitConverter.ToUInt32(data, 20), XPelsPerMeter = BitConverter.ToInt32(data, 24), YPelsPerMeter = BitConverter.ToInt32(data, 28), ClrUsed = BitConverter.ToUInt32(data, 32), ClrImportant = BitConverter.ToUInt32(data, 36) }; // Read the bitmask fields if necessary for BITMAPINFOHEADER if (headerSize == (int)BmpNativeStructuresSizes.BITMAPINFOHEADER) { if (bmpInfo.Compression == BmpCompression.BitFields) { // Windows NT 4 and Windows 98 RGB bitmask extention to BITMAPINFOHEADER if ((bmpInfo.ComputePaletteStorageSize((long)this.fileHeader.Offset) - 3) >= bmpInfo.ClrUsed) { this.currentStream.Read(data, 40, 3 * sizeof(uint)); headerSize = (int)BmpNativeStructuresSizes.BITMAPINFOHEADER_NT; } else { switch ((int)bmpInfo.BitsPerPixel) { case (int)BmpBitsPerPixel.RGB16: bmpInfo.RedMask = BmpStandardBitmask.RGB555RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB555GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB555BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB555AlphaMask; break; case (int)BmpBitsPerPixel.RGB24: bmpInfo.RedMask = BmpStandardBitmask.RGB888RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB888GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB888BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; case (int)BmpBitsPerPixel.RGB32: bmpInfo.RedMask = BmpStandardBitmask.RGB8888RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB8888GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB8888BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; } } } else if (bmpInfo.Compression == BmpCompression.AlphaBitFields) { // Windows CE 5 RGBA bitmask extention to BITMAPINFOHEADER if ((bmpInfo.ComputePaletteStorageSize((long)this.fileHeader.Offset) - 4) >= bmpInfo.ClrUsed) { this.currentStream.Read(data, 40, 4 * sizeof(uint)); headerSize = (int)BmpNativeStructuresSizes.BITMAPINFOHEADER_CE; } else { switch ((int)bmpInfo.BitsPerPixel) { case (int)BmpBitsPerPixel.RGB16: bmpInfo.RedMask = BmpStandardBitmask.RGB555RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB555GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB555BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB555AlphaMask; break; case (int)BmpBitsPerPixel.RGB24: bmpInfo.RedMask = BmpStandardBitmask.RGB888RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB888GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB888BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; case (int)BmpBitsPerPixel.RGB32: bmpInfo.RedMask = BmpStandardBitmask.RGB8888RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB8888GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB8888BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; } } } } // Read the RGB bitmasks if (headerSize >= (int)BmpNativeStructuresSizes.BITMAPINFOHEADER_NT) { // RGB bitmasks bmpInfo.RedMask = BitConverter.ToUInt32(data, 40); bmpInfo.GreenMask = BitConverter.ToUInt32(data, 44); bmpInfo.BlueMask = BitConverter.ToUInt32(data, 48); if (headerSize >= (int)BmpNativeStructuresSizes.BITMAPINFOHEADER_CE) { // Alpha bitmask bmpInfo.AlphaMask = BitConverter.ToUInt32(data, 52); } else { switch ((int)bmpInfo.BitsPerPixel) { case (int)BmpBitsPerPixel.RGB16: bmpInfo.AlphaMask = BmpStandardBitmask.RGB555AlphaMask; break; case (int)BmpBitsPerPixel.RGB24: bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; case (int)BmpBitsPerPixel.RGB32: bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; } } } else { switch ((int)bmpInfo.BitsPerPixel) { case (int)BmpBitsPerPixel.RGB16: bmpInfo.RedMask = BmpStandardBitmask.RGB555RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB555GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB555BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB555AlphaMask; break; case (int)BmpBitsPerPixel.RGB24: bmpInfo.RedMask = BmpStandardBitmask.RGB888RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB888GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB888BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; case (int)BmpBitsPerPixel.RGB32: bmpInfo.RedMask = BmpStandardBitmask.RGB8888RedMask; bmpInfo.GreenMask = BmpStandardBitmask.RGB8888GreenMask; bmpInfo.BlueMask = BmpStandardBitmask.RGB8888BlueMask; bmpInfo.AlphaMask = BmpStandardBitmask.RGB888AlphaMask; break; } } return(bmpInfo); }
/// <summary> /// Parses the <see cref="BmpInfoHeader"/> from the stream, assuming it uses the <see cref="OS2InfoHeaderV2"/> format. /// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183372.aspx">See this MSDN link for more information.</seealso> /// </summary> /// <param name="data">Header bytes read from the stream</param> /// <param name="headerSize">Header maximum bytes to read from <c>data</c></param> /// <returns>Parsed header</returns> private BmpInfoHeader ParseBitmapOS2V2Header(byte[] data, int headerSize) { // Invalid header size if ((headerSize < (int)BmpNativeStructuresSizes.OS22XBITMAPHEADER_MIN) || (headerSize > (int)BmpNativeStructuresSizes.OS22XBITMAPHEADER_MAX)) { throw new NotSupportedException($"This kind of IBM OS/2 bitmap files (header size $headerSize) is not supported."); } if ((headerSize == (int)BmpNativeStructuresSizes.BITMAPINFOHEADER) || (headerSize == (int)BmpNativeStructuresSizes.BITMAPINFOHEADER_NT) || (headerSize == (int)BmpNativeStructuresSizes.BITMAPINFOHEADER_CE)) { // Check to see if is valid Windows DIB v3 header uint bitsPerPixel = BitConverter.ToUInt16(data, 14); if ((bitsPerPixel == (uint)BmpBitsPerPixel.RGB16) || (bitsPerPixel == (uint)BmpBitsPerPixel.RGB32)) { // OS/2 bitmaps don’t support 16-bit and 32-bit per pixel formats, so must be a Windows DIB v3 header // The bitfields and alpha-bitfield compression extensions for Windows NT and CE, hare detected here return(this.ParseBitmapInfoHeader(data, headerSize)); } else { uint compression = BitConverter.ToUInt32(data, 16); if ((headerSize == (int)BmpNativeStructuresSizes.BITMAPINFOHEADER) && (compression != (uint)BmpOS2Compression.Huffman1D) && (compression != (uint)BmpOS2Compression.RLE24)) { // OS/2 40 bytes DIB v2 header is the same format as the Windows 40 byte DIB v3 header // Only 1-bit Huffman-1D and 24-bit RLE-24 compressed DIBs hare different (Windows DIB headers don't support them) return(this.ParseBitmapInfoHeader(data, headerSize)); } } } // OK, it's an OS/2 DIB v2 header var bmpInfo = new BmpInfoHeader { // Mark the header as an IBM OS/2 v2 informatiom header for the DIB IsOS2InfoHeaderV2 = true, // Minimum fields HeaderSize = BitConverter.ToUInt32(data, 0), Width = BitConverter.ToInt32(data, 4), Height = BitConverter.ToInt32(data, 8), Planes = BitConverter.ToUInt16(data, 12), BitsPerPixel = BitConverter.ToUInt16(data, 14), // Reset the fields that may not be defined Compression = BmpCompression.RGB, ImageSize = 0, XPelsPerMeter = 0, YPelsPerMeter = 0, ClrUsed = 0, ClrImportant = 0, Os2Units = 0, Os2Reserved = 0, Os2Recording = 0, Os2Rendering = BmpOS2CompressionHalftoning.NoHalftoning, Os2Size1 = 0, Os2Size2 = 0, Os2ColorEncoding = 0, Os2Identifier = 0 }; // Extra fields if (headerSize > (int)BmpNativeStructuresSizes.OS22XBITMAPHEADER_MIN) { // IBM OS/2 v2 pixel data compression method used bmpInfo.Os2Compression = (BmpOS2Compression)BitConverter.ToUInt32(data, 16); if ((bmpInfo.Os2Compression == BmpOS2Compression.RGB) || (bmpInfo.Os2Compression == BmpOS2Compression.RLE8) || (bmpInfo.Os2Compression == BmpOS2Compression.RLE4)) { // It's the same as the Microsoft Windows compression, so update it bmpInfo.Compression = (BmpCompression)((int)bmpInfo.Os2Compression); } if (headerSize > 20) { bmpInfo.ImageSize = BitConverter.ToUInt32(data, 20); if (headerSize > 24) { bmpInfo.XPelsPerMeter = BitConverter.ToInt32(data, 24); if (headerSize > 28) { bmpInfo.YPelsPerMeter = BitConverter.ToInt32(data, 28); if (headerSize > 32) { bmpInfo.ClrUsed = BitConverter.ToUInt32(data, 32); if (headerSize > 36) { bmpInfo.ClrImportant = BitConverter.ToUInt32(data, 36); if (headerSize > 40) { bmpInfo.Os2Units = BitConverter.ToUInt16(data, 40); if (headerSize > 42) { bmpInfo.Os2Reserved = BitConverter.ToUInt16(data, 42); if (headerSize > 44) { bmpInfo.Os2Recording = BitConverter.ToUInt16(data, 44); if (headerSize > 46) { bmpInfo.Os2Rendering = (BmpOS2CompressionHalftoning)BitConverter.ToUInt16(data, 46); if (headerSize > 48) { bmpInfo.Os2Size1 = BitConverter.ToUInt32(data, 48); if (headerSize > 52) { bmpInfo.Os2Size2 = BitConverter.ToUInt32(data, 52); if (headerSize > 56) { bmpInfo.Os2ColorEncoding = BitConverter.ToUInt32(data, 56); if (headerSize > 60) { bmpInfo.Os2Identifier = BitConverter.ToUInt32(data, 60); } } } } } } } } } } } } } } return(bmpInfo); }
/// <summary> /// Reads the <see cref="BmpInfoHeader"/> from the stream. /// </summary> private void ReadInfoHeader() { byte[] data = new byte[(int)BmpNativeStructuresSizes.BITMAPV5HEADER]; // read header size this.currentStream.Read(data, 0, sizeof(uint)); int headerSize = BitConverter.ToInt32(data, 0); // read the rest of the header int skipAmmount = 0; if (headerSize > (int)BmpNativeStructuresSizes.BITMAPV5HEADER) { // FIXME: If the header size is bigger than BITMAPV5HEADER structure, this is a bug skipAmmount = headerSize - (int)BmpNativeStructuresSizes.BITMAPV5HEADER; headerSize = (int)BmpNativeStructuresSizes.BITMAPV5HEADER; } this.currentStream.Read(data, sizeof(uint), headerSize - sizeof(uint)); switch (headerSize) { // Windows DIB Info Header v2 and OS/2 DIB Info Header v1 case (int)BmpNativeStructuresSizes.BITMAPCOREHEADER: this.infoHeader = this.ParseBitmapCoreHeader(data); break; // OS/2 DIB Info Header v2 minimum size case (int)BmpNativeStructuresSizes.OS22XBITMAPHEADER_MIN: // OS/2 DIB Info Header v2 full size case (int)BmpNativeStructuresSizes.OS22XBITMAPHEADER: this.infoHeader = this.ParseBitmapOS2V2Header(data, headerSize); break; // Windows DIB Info Header v4 case (int)BmpNativeStructuresSizes.BITMAPV4HEADER: this.infoHeader = this.ParseBitmapInfoHeader(data, headerSize); break; // Windows DIB Info Header v5 case (int)BmpNativeStructuresSizes.BITMAPV5HEADER: this.infoHeader = this.ParseBitmapInfoHeader(data, headerSize); break; default: if ((headerSize > (int)BmpNativeStructuresSizes.OS22XBITMAPHEADER_MIN) && (headerSize < (int)BmpNativeStructuresSizes.OS22XBITMAPHEADER_MAX)) { // OS/2 DIB Info Header v2 variable size this.infoHeader = this.ParseBitmapOS2V2Header(data, headerSize); break; } else { // UPS! Unknow DIB header throw new NotSupportedException($"This kind of bitmap files (header size $headerSize) is not supported."); } } // Check if the DIB is top-down (negative height => origin is the upper-left corner) // or bottom-up (positeve height => origin is the lower-left corner) if (this.infoHeader.Height < 0) { this.infoHeader.IsTopDown = true; this.infoHeader.Height = -this.infoHeader.Height; } // Check if the DIB is pre-rotated if ((SourcePreRotateMask & (uint)this.infoHeader.Compression) == SourcePreRotateMask) { this.infoHeader.IsSourcePreRotate = true; this.infoHeader.Compression = (BmpCompression)((uint)this.infoHeader.Compression & (~SourcePreRotateMask)); } // skip the remaining header because we can't read those parts this.currentStream.Skip(skipAmmount); }