forked from mike42/gfx-php
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request mike42#46 from mike42/feature/35-bmp
Implement RLE8 decode for BMP files
- Loading branch information
Showing
7 changed files
with
521 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
namespace Mike42\GfxPhp\Codec\Bmp; | ||
|
||
use Exception; | ||
|
||
class Rle8Decoder | ||
{ | ||
const RLE_ESCAPE = 0; | ||
const RLE_END_LINE = 0; | ||
const RLE_END_BITMAP = 1; | ||
const RLE_JUMP = 2; | ||
|
||
public function decode(string $compressedImgData, int $width, int $height) : string | ||
{ | ||
// Read into numeric input. | ||
$inpNum = array_values(unpack("C*", $compressedImgData)); | ||
$outpNum = $this -> decodeNumbers($inpNum, $width, $height); | ||
// Back to string | ||
$outStringArr = []; | ||
$rowWidth = intdiv((8 * $width + 31), 32) * 4; // Padding to 4-byte boundary: Can this be simplified? | ||
$padding = str_repeat("\0", $rowWidth - $width); | ||
foreach ($outpNum as $row) { | ||
$outStringArr[] = pack("C*", ...$row); | ||
} | ||
return implode($padding, $outStringArr) . $padding; | ||
} | ||
|
||
public function decodeNumbers(array $inpNum, int $width, int $height) : array | ||
{ | ||
// Initialize canvas | ||
$buffer = new RleCanvas($width, $height); | ||
// read input data, 2 byes at a time | ||
$i = 0; | ||
$len = intdiv(count($inpNum), 2) * 2; | ||
while ($i < $len) { | ||
$firstByte = $inpNum[$i]; | ||
$secondByte = $inpNum[$i + 1]; | ||
$i += 2; | ||
if ($firstByte === self::RLE_ESCAPE) { | ||
if ($secondByte === self::RLE_END_LINE) { | ||
$buffer -> endOfLine(); | ||
} else if ($secondByte === self::RLE_END_BITMAP) { | ||
$buffer -> endOfBitmap(); | ||
} else if ($secondByte === self::RLE_JUMP) { | ||
// "Delta". | ||
if ($i + 2 > $len) { // Need 2 more bytes to find out how far to jump | ||
throw new Exception("Unexpected EOF"); | ||
} | ||
$deltaX = $inpNum[$i]; | ||
$deltaY = $inpNum[$i + 1]; | ||
$i += 2; | ||
$buffer -> delta($deltaX, $deltaY); | ||
} else { | ||
// "Absolute run". Paste the requested number of bytes onto the canvas | ||
$absoluteLen = $secondByte; | ||
if ($i + $absoluteLen > $len) { | ||
throw new Exception("Unexpected EOF"); | ||
} | ||
$bytesToPaste = array_slice($inpNum, $i, $absoluteLen); | ||
$i += $absoluteLen; | ||
if ($absoluteLen % 2 != 0) { | ||
// skip a padding byte too | ||
$i++; | ||
} | ||
$buffer -> absolute($bytesToPaste); | ||
} | ||
} else { | ||
$buffer -> repeat($secondByte, $firstByte); | ||
} | ||
} | ||
return $buffer -> getContents(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<?php | ||
|
||
namespace Mike42\GfxPhp\Codec\Bmp; | ||
|
||
use Exception; | ||
|
||
/** | ||
* 2D canvas for writing RLE-encoded BMP data into. A cursor is moved based on the commands issued, and an Exception | ||
* is thrown if the data goes outside the canvas boundary, or if the data continues after endOfBitmap is called. | ||
*/ | ||
class RleCanvas | ||
{ | ||
private $buffer; | ||
private $cursorX; | ||
private $cursorY; | ||
private $width; | ||
private $height; | ||
private $complete; | ||
|
||
public function __construct(int $width, int $height) | ||
{ | ||
$this -> cursorX = 0; | ||
$this -> cursorY = 0; | ||
$this -> width = $width; | ||
$this -> height = $height; | ||
$this -> complete = false; | ||
// Initialise empty space | ||
$tmp = []; | ||
for ($y = 0; $y < $height; $y++) { | ||
$tmp[] = array_fill(0, $width, 0); | ||
} | ||
$this->buffer = $tmp; | ||
} | ||
|
||
public function delta(int $deltaX, int $deltaY) | ||
{ | ||
$this -> cursorX += $deltaX; | ||
$this -> cursorY += $deltaY; | ||
} | ||
|
||
public function endOfLine() | ||
{ | ||
$this -> cursorY++; | ||
$this -> cursorX = 0; | ||
} | ||
|
||
public function endOfBitmap() | ||
{ | ||
$this -> complete = true; | ||
} | ||
|
||
public function set(int $val) | ||
{ | ||
// Range check when we attempt to write the pixel. | ||
if ($this -> cursorY < 0 || $this -> cursorY >= $this -> height) { | ||
throw new Exception("Bitmap compressed data exceeds image boundary; file is not valid. Y-overflow"); | ||
} | ||
if ($this -> cursorX < 0 || $this -> cursorX >= $this -> width) { | ||
throw new Exception("Bitmap compressed data exceeds image boundary; file is not valid. X-overflow"); | ||
} | ||
if ($this -> complete) { | ||
throw new Exception("Bitmap compressed data continued after end-of-bitmap code was found; file is not valid."); | ||
} | ||
// Write the pixel | ||
$this -> buffer[$this -> cursorY][$this -> cursorX] = $val; | ||
$this -> cursorX++; | ||
} | ||
|
||
public function absolute(array $values) | ||
{ | ||
for ($i = 0; $i < count($values); $i++) { | ||
$this -> set($values[$i]); | ||
} | ||
} | ||
|
||
public function repeat(int $val, int $times) | ||
{ | ||
for ($j = 0; $j < $times; $j++) { | ||
$this -> set($val); | ||
} | ||
} | ||
|
||
public function getContents() | ||
{ | ||
return $this -> buffer; | ||
} | ||
} |
Oops, something went wrong.