Skip to content

Commit

Permalink
add 16-bit decoding with default masks
Browse files Browse the repository at this point in the history
  • Loading branch information
mike42 committed Sep 27, 2019
1 parent 2badddd commit 5d98fe3
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 2 deletions.
46 changes: 45 additions & 1 deletion src/Mike42/GfxPhp/Codec/Bmp/BmpFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public static function fromBinary(DataInputStream $data) : BmpFile
$infoHeader -> bpp != 32) {
throw new Exception("Bit depth " . $infoHeader -> bpp . " not valid.");
} else if ($infoHeader -> bpp === 0 ||
$infoHeader -> bpp === 16 ||
$infoHeader -> bpp === 32) {
// Fail early to give a clearer error for the things which aren't tested yet
throw new Exception("Bit depth " . $infoHeader -> bpp . " not implemented.");
Expand Down Expand Up @@ -182,12 +181,57 @@ public function toRasterImage() : RasterImage
return IndexedRasterImage::create($this -> infoHeader -> width, $this -> infoHeader -> height, $expandedData, $this -> palette);
} else if ($this -> infoHeader -> bpp == 8) {
return IndexedRasterImage::create($this -> infoHeader -> width, $this -> infoHeader -> height, $this -> uncompressedData, $this -> palette);
} else if ($this -> infoHeader -> bpp == 16) {
// Default bit counts only.
// TODO check for different numbers of bits in info header and/or BITFIELDS
$redBits = 5;
$blueBits = 5;
$greenBits = 5;
$expandedData = $this -> read16Bit($this -> uncompressedData, $redBits, $blueBits, $greenBits);
return RgbRasterImage::create($this -> infoHeader -> width, $this -> infoHeader -> height, $expandedData);
} else if ($this -> infoHeader -> bpp == 24) {
return RgbRasterImage::create($this -> infoHeader -> width, $this -> infoHeader -> height, $this -> uncompressedData);
}
throw new Exception("Unknown bit depth " . $this -> infoHeader -> bpp);
}

public static function read16bit(array $inpBytes, int $redBits = 5, int $blueBits = 5, int $greenBits = 5) : array
{
// Fill output array to 1.5 times the size of the input array
$pixelCount = intdiv(count($inpBytes), 2);
$outpBytes = array_fill(0, $pixelCount * 3, 0);
// Determine how many bits right to shift to get requested B, G, R values from the 16-bit input to the RHS.
$blueReadShift = 0;
$greenReadShift = $blueBits + $blueReadShift;
$redReadShift = $greenBits + $greenReadShift;
// Set up B, G, R masks to extract the requested number of bits
$blueReadMask = (0xff >> (8 - $blueBits));
$greenReadMask = (0xff >> (8 - $greenBits));
$redReadMask = (0xff >> (8 - $redBits));
// How many bits left to shift to get the extracted value to take up 8 bits
$blueWriteShift = 8 - $blueBits;
$greenWriteShift = 8 - $greenBits;
$redWriteShift = 8 - $redBits;
// Number of bits right to shift to get the extracted value to top up otherwise unset least-significant bits.
// This allows us to map values to the full 0-255 range, as long as at least 4 bits are used.
$blueWriteLsbShift = $blueBits - $blueWriteShift;
$greenWriteLsbShift = $greenBits - $greenWriteShift;
$redWriteLsbShift = $redBits - $redWriteShift;
for ($i = 0; $i < $pixelCount; $i++) {
// Extract little-endian color code in 16 bit space
$inpColor = ($inpBytes[$i * 2 + 1] << 8) + ($inpBytes[$i * 2]);
// Get 5-bit red, blue and green components
$blueLevel = ($inpColor >> $blueReadShift) & $blueReadMask;
$greenLevel = ($inpColor >> $greenReadShift) & $greenReadMask;
$redLevel = ($inpColor >> $redReadShift) & $redReadMask;
// Store as 8-bit components
$outpBytes[$i * 3] = ($redLevel << $redWriteShift) + ($redLevel >> $redWriteLsbShift);
$outpBytes[$i * 3 + 1] = ($greenLevel << $greenWriteShift) + ($greenLevel >> $greenWriteLsbShift);
$outpBytes[$i * 3 + 2] = ($blueLevel << $blueWriteShift) + ($blueLevel >> $blueWriteLsbShift);
}
return $outpBytes;
}

public static function transformRevString(&$item, $key)
{
// Convert RGB to BGR
Expand Down
1 change: 0 additions & 1 deletion test/integration/BmpsuiteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ function test_rgb16_565pal()

function test_rgb16()
{
$this -> markTestSkipped("Not implemented");
$img = $this -> loadImage("g/rgb16.bmp");
$this -> assertEquals(127, $img -> getWidth());
$this -> assertEquals(64, $img -> getHeight());
Expand Down
26 changes: 26 additions & 0 deletions test/unit/Codec/Bmp/BmpFileTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Mike42\GfxPhp\Codec\Bmp;


use PHPUnit\Framework\TestCase;

class GifCodecTest extends TestCase
{

/**
* Decode various 16-bit pixels with the default 5-5-5 bit depths
*/
public function testReadDefaults()
{
// 5-5-5, little-endian encoded as 2 bytes.
$this -> assertEquals([0, 0, 0], BmpFile::read16bit([0x00, 0x00])); // Black
$this -> assertEquals([0, 0, 0], BmpFile::read16bit([0x00, 0x80])); // Also black (MSB ignored)
$this -> assertEquals([255, 255, 255], BmpFile::read16bit([0xff, 0xff])); // White
$this -> assertEquals([0, 0, 255], BmpFile::read16bit([0x1f, 0])); // Blue
$this -> assertEquals([0, 255, 0], BmpFile::read16bit([0xe0, 0x03])); // Green
$this -> assertEquals([255, 0, 0], BmpFile::read16bit([0x00, 0x7c])); // Red
}


}

0 comments on commit 5d98fe3

Please sign in to comment.