/// <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> /// Produce uncompressed bitmap data from a RLE8 stream. /// </summary> /// <remarks> /// RLE8 is a 2-byte run-length encoding. /// <br/>If first byte is 0, the second byte may have special meaning. /// <br/>Otherwise, the first byte is the length of the run and second byte is the color for the run. /// </remarks> /// <param name="w">The width of the bitmap.</param> /// <param name="buffer">Buffer for uncompressed data.</param> private void UncompressRle8(int w, Span <byte> buffer) { #if NETCOREAPP2_1 Span <byte> cmd = stackalloc byte[2]; #else byte[] cmd = new byte[2]; #endif int count = 0; while (count < buffer.Length) { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); } if (cmd[0] == RleCommand) { switch (cmd[1]) { case RleEndOfBitmap: return; case RleEndOfLine: int extra = count % w; if (extra > 0) { count += w - extra; } break; case RleDelta: int dx = this.stream.ReadByte(); int dy = this.stream.ReadByte(); count += (w * dy) + dx; break; default: // If the second byte > 2, we are in 'absolute mode' // Take this number of bytes from the stream as uncompressed data int length = cmd[1]; byte[] run = new byte[length]; this.stream.Read(run, 0, run.Length); run.AsSpan().CopyTo(buffer.Slice(count)); count += run.Length; // Absolute mode data is aligned to two-byte word-boundary int padding = length & 1; this.stream.Skip(padding); break; } } else { int max = count + cmd[0]; // as we start at the current count in the following loop, max is count + cmd[0] byte cmd1 = cmd[1]; // store the value to avoid the repeated indexer access inside the loop for (; count < max; count++) { buffer[count] = cmd1; } } } }
/// <summary> /// Produce uncompressed bitmap data from a RLE4 stream. /// </summary> /// <remarks> /// RLE4 is a 2-byte run-length encoding. /// <br/>If first byte is 0, the second byte may have special meaning. /// <br/>Otherwise, the first byte is the length of the run and second byte contains two color indexes. /// </remarks> /// <param name="w">The width of the bitmap.</param> /// <param name="buffer">Buffer for uncompressed data.</param> private void UncompressRle4(int w, Span <byte> buffer) { #if NETCOREAPP2_1 Span <byte> cmd = stackalloc byte[2]; #else byte[] cmd = new byte[2]; #endif int count = 0; while (count < buffer.Length) { if (this.stream.Read(cmd, 0, cmd.Length) != 2) { BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); } if (cmd[0] == RleCommand) { switch (cmd[1]) { case RleEndOfBitmap: return; case RleEndOfLine: int extra = count % w; if (extra > 0) { count += w - extra; } break; case RleDelta: int dx = this.stream.ReadByte(); int dy = this.stream.ReadByte(); count += (w * dy) + dx; break; default: // If the second byte > 2, we are in 'absolute mode'. // The second byte contains the number of color indexes that follow. int max = cmd[1]; int bytesToRead = (max + 1) / 2; byte[] run = new byte[bytesToRead]; this.stream.Read(run, 0, run.Length); int idx = 0; for (int i = 0; i < max; i++) { byte twoPixels = run[idx]; if (i % 2 == 0) { byte leftPixel = (byte)((twoPixels >> 4) & 0xF); buffer[count++] = leftPixel; } else { byte rightPixel = (byte)(twoPixels & 0xF); buffer[count++] = rightPixel; idx++; } } // Absolute mode data is aligned to two-byte word-boundary int padding = bytesToRead & 1; this.stream.Skip(padding); break; } } else { int max = cmd[0]; // The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits. byte twoPixels = cmd[1]; byte rightPixel = (byte)(twoPixels & 0xF); byte leftPixel = (byte)((twoPixels >> 4) & 0xF); for (int idx = 0; idx < max; idx++) { if (idx % 2 == 0) { buffer[count] = leftPixel; } else { buffer[count] = rightPixel; } count++; } } } }
/// <summary> /// Decodes the image from the specified this._stream and sets /// the data to image. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="stream">The stream, where the image should be /// decoded from. Cannot be null (Nothing in Visual Basic).</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="stream"/> is null.</para> /// </exception> /// <returns>The decoded image.</returns> public Image <TPixel> Decode <TPixel>(Stream stream) where TPixel : struct, IPixel <TPixel> { try { int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); var image = new Image <TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metaData); Buffer2D <TPixel> pixels = image.GetRootFramePixelBuffer(); switch (this.infoHeader.Compression) { case BmpCompression.RGB: if (this.infoHeader.BitsPerPixel == 32) { if (this.bmpMetaData.InfoHeaderType == BmpInfoHeaderType.WinVersion3) { this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } else { this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } } else if (this.infoHeader.BitsPerPixel == 24) { this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } else if (this.infoHeader.BitsPerPixel == 16) { this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } else if (this.infoHeader.BitsPerPixel <= 8) { this.ReadRgbPalette( pixels, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, bytesPerColorMapEntry, inverted); } break; case BmpCompression.RLE8: case BmpCompression.RLE4: this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); break; case BmpCompression.BitFields: this.ReadBitFields(pixels, inverted); break; default: BmpThrowHelper.ThrowNotSupportedException("Does not support this kind of bitmap files."); break; } return(image); } catch (IndexOutOfRangeException e) { throw new ImageFormatException("Bitmap does not have a valid format.", e); } }