Skip to content

Commit

Permalink
[WIP] RLE8 decode implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
mike42 committed Jul 11, 2019
1 parent 1647c94 commit 944c636
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 3 deletions.
122 changes: 122 additions & 0 deletions src/Mike42/GfxPhp/Codec/Bmp/BmpFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public static function fromBinary(DataInputStream $data) : BmpFile
$compressedImgSizeBytes = $uncompressedImgSizeBytes;
} else {
$compressedImgSizeBytes = $infoHeader -> compressedSize;
// Limit height to prevent insane allocations during decompression if file is corrupt
if ($infoHeader -> width > 65535 || $infoHeader -> height > 65535 || $infoHeader -> width < 0 || $infoHeader -> height < 0) {
throw new Exception("Image size " . $infoHeader -> width . "x" . $infoHeader -> height . " is outside the supported range.");
}
}
$compressedImgData = $data -> read($compressedImgSizeBytes);
// De-compress if necessary
Expand All @@ -73,6 +77,15 @@ public static function fromBinary(DataInputStream $data) : BmpFile
$uncompressedImgData = $compressedImgData;
break;
case BmpInfoHeader::B1_RLE8:
if ($infoHeader -> bpp !== 8) {
throw new Exception("RLE8 compression only valid for 8-bit images");
}
$uncompressedImgData = self::rle8decode($compressedImgData, $infoHeader -> width, $infoHeader -> height);
$actualSize = strlen($uncompressedImgData);
if ($uncompressedImgSizeBytes !== $actualSize) {
throw new Exception("RLE8 decode failed. Expected $uncompressedImgSizeBytes bytes uncompressed, got $actualSize");
}
break;
case BmpInfoHeader::B1_RLE4:
case BmpInfoHeader::B1_BITFILEDS:
case BmpInfoHeader::B1_JPEG:
Expand Down Expand Up @@ -107,6 +120,115 @@ public static function fromBinary(DataInputStream $data) : BmpFile
return new BmpFile($fileHeader, $infoHeader, $dataArray, $colorTable);
}

private static function rle8decode(string $compressedImgData, int $width, int $height)
{
// Padding to 4-byte boundary: Can this be simplified?
$rowWidth = intdiv((8 * $width + 31), 32) * 4;
// Initialize buffer to 0's
$outpNum = [];
for ($y = 0; $y < $height; $y++) {
$outpNum[] = array_fill(0, $rowWidth, 0);
}
// read input data into 2d buffer
$inpNum = array_values(unpack("C*", $compressedImgData));
$x = 0;
$y = 0;
$i = 0;
$len = intdiv(count($inpNum), 2) * 2;
while ($i < $len) {
$firstByte = $inpNum[$i];
$secondByte = $inpNum[$i + 1];
$i += 2;
//echo "Pair $firstByte $secondByte\n";
if ($firstByte === 0) {
if ($secondByte == 0) {
//echo "End of line\n";
// EOL
$x = 0;
$y++;
if ($y >= $height) {
//echo "ERROR Y overflow on EOL, breaking.";
break;
}
} else if ($secondByte == 1) {
//echo "End of bitmap\n";
// End of bitmap
break;
} else if ($secondByte == 2) {
if ($i >= $len) {
throw new Exception("Unexpected EOF");
}
$firstDeltaByte = $inpNum[$i];
$secondDeltaByte = $inpNum[$i + 1];
//echo "Delta $firstDeltaByte, $secondDeltaByte\n";
$i += 2;
$x += $firstDeltaByte;
$y += $secondDeltaByte;
if ($x == $width) {
$x = 0;
//$y++;
// echo "Wrapping line x=$x, y=$y\n";
} else if ($x > $width) {
throw new Exception("Overflow.");
}
if ($y >= $height) {
// Overflow
//echo "ERROR Y overflow on jump, breaking.";
throw new Exception("Bitmap compressed data exceeds image boundary; file is not valid.");
}
} else {
//echo "Absolute run of $secondByte bytes\n";
$absoluteLen = $secondByte;
for ($j = 0; $j < $absoluteLen; $j++) {
//echo "Setting byte " . $inpNum[$i + $j] . " x=$x, y=$y (j=$j)\n";
$outpNum[$y][$x] = $inpNum[$i + $j];
$x++;
if ($x == $width) {
$x = 0;
//$y++;
// echo "Wrapping line x=$x, y=$y\n";
} else if ($x > $width) {
throw new Exception("Overflow..");
}
if ($y >= $height) {
// Overflow
//echo "ERROR Y overflow on line-wrap, breaking.";
break 2;
}
}
$i += $absoluteLen;
if ($absoluteLen % 2 != 0) {
//echo "Skipped padding byte\n";
$i++; // skip a padding byte too
}
}
} else {
//echo "Repeat $firstByte instances of $secondByte\n";
for ($j = 0; $j < $firstByte; $j++) {
//echo "Setting byte " . $secondByte . " x=$x, y=$y (j=$j)\n";
$outpNum[$y][$x] = $secondByte;
$x++;
if ($x >= $width) {
$x = 0;
//$y++;
//echo "Wrapping line x=$x, y=$y\n";
}
if ($y >= $height) {
// Overflow
//echo "ERROR Y overflow on line-wrap, breaking.";
break 2;
}
}
}
}
// Back to string
$outStringArr = [];
foreach ($outpNum as $row) {
$outStringArr[] = pack("C*", ...$row);
}
return implode("", $outStringArr); //str_repeat("\0", $rowWidth * $height);
}

public function toRasterImage() : RasterImage
{
if ($this -> infoHeader -> bpp == 1) {
Expand Down
3 changes: 0 additions & 3 deletions test/integration/BmpsuiteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ function test_pal8os2() {
}

function test_pal8rle() {
$this -> markTestSkipped("Not implemented");
$img = $this -> loadImage("g/pal8rle.bmp");
$this -> assertEquals(127, $img -> getWidth());
$this -> assertEquals(64, $img -> getHeight());
Expand Down Expand Up @@ -392,14 +391,12 @@ function test_pal8oversizepal() {
}

function test_pal8rlecut() {
$this -> markTestSkipped("Not implemented");
$img = $this -> loadImage("q/pal8rlecut.bmp");
$this -> assertEquals(127, $img -> getWidth());
$this -> assertEquals(64, $img -> getHeight());
}

function test_pal8rletrns() {
$this -> markTestSkipped("Not implemented");
$img = $this -> loadImage("q/pal8rletrns.bmp");
$this -> assertEquals(127, $img -> getWidth());
$this -> assertEquals(64, $img -> getHeight());
Expand Down

0 comments on commit 944c636

Please sign in to comment.