-
Notifications
You must be signed in to change notification settings - Fork 4
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 #41 from mike42/feature/40-wbmp
[WIP] WBMP implementation
- Loading branch information
Showing
7 changed files
with
236 additions
and
3 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
Binary file not shown.
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,103 @@ | ||
<?php | ||
|
||
namespace Mike42\GfxPhp\Codec; | ||
|
||
use Exception; | ||
use Mike42\GfxPhp\BlackAndWhiteRasterImage; | ||
use Mike42\GfxPhp\Codec\Common\DataBlobInputStream; | ||
use Mike42\GfxPhp\Codec\ImageDecoder; | ||
use Mike42\GfxPhp\Codec\ImageEncoder; | ||
use Mike42\GfxPhp\RasterImage; | ||
|
||
class WbmpCodec implements ImageDecoder, ImageEncoder | ||
{ | ||
protected static $instance = null; | ||
|
||
public function identify(string $blob): string | ||
{ | ||
$wbmpMagic = substr($blob, 0, 2); | ||
if ($wbmpMagic == "\x00\x00") { | ||
// Wireless Application Protocol Bitmap | ||
return "wbmp"; | ||
} | ||
return ""; | ||
} | ||
|
||
public function decode(string $blob): RasterImage | ||
{ | ||
$data = DataBlobInputStream::fromBlob($blob); | ||
$header = $data -> read(2); | ||
if ($header != "\x00\x00") { | ||
throw new Exception("Not a WBMP file"); | ||
} | ||
$width = $this -> readInt($data); | ||
$height = $this -> readInt($data); | ||
$bytesPerRow = intdiv($width + 7, 8); | ||
$expectedBytes = $bytesPerRow * $height; | ||
$binaryData = $data -> read($expectedBytes); | ||
$dataUnpacked = unpack("C*", $binaryData); | ||
$dataValues = array_values($dataUnpacked); | ||
// 1 for white, 0 for black (opposite) | ||
$image = BlackAndWhiteRasterImage::create($width, $height, $dataValues); | ||
$image -> invert(); | ||
return $image; | ||
} | ||
|
||
public function readInt(DataBlobInputStream $data) : int | ||
{ | ||
$i = 0; | ||
$ret = 0; | ||
do { | ||
$byte = ord($data -> read(1)); | ||
$ret = ($ret << 7) | ($byte & 0x7F); | ||
$continuation = $byte >> 7 == 1; | ||
$i++; | ||
} while ($continuation && $i < 4); // Limit to 4 bytes to avoid overflow. | ||
if ($continuation) { | ||
throw new Exception("WBMP image size too large, file may be corrupt"); | ||
} | ||
return $ret; | ||
} | ||
|
||
public function writeInt(int $val) : string | ||
{ | ||
$i = 0; | ||
$ret = chr($val & 0x7F); | ||
$val >>= 7; | ||
while ($val > 0 && $i < 3) { | ||
$byteVal = ($val & 0x7F) | 0x80; | ||
$ret = chr($byteVal) . $ret; | ||
$val >>= 7; | ||
$i++; | ||
} | ||
if ($val > 0) { | ||
throw new Exception("WBMP image size too large."); | ||
} | ||
return $ret; | ||
} | ||
|
||
public function getDecodeFormats(): array | ||
{ | ||
return ["wbmp"]; | ||
} | ||
|
||
public function encode(RasterImage $image, string $format): string | ||
{ | ||
$image = $image = $image -> toBlackAndWhite(); | ||
$image -> invert(); | ||
return "\x00\x00" . $this -> writeInt($image -> getWidth()) . $this -> writeInt($image -> getHeight()) . $image -> getRasterData(); | ||
} | ||
|
||
public function getEncodeFormats(): array | ||
{ | ||
return ["wbmp"]; | ||
} | ||
|
||
public static function getInstance() | ||
{ | ||
if (self::$instance === null) { | ||
self::$instance = new WbmpCodec(); | ||
} | ||
return self::$instance; | ||
} | ||
} |
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,89 @@ | ||
<?php | ||
|
||
use Mike42\GfxPhp\BlackAndWhiteRasterImage; | ||
use Mike42\GfxPhp\Codec\Common\DataBlobInputStream; | ||
use Mike42\GfxPhp\Codec\WbmpCodec; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class WbmpCodecTest extends TestCase { | ||
const WBMP_IMAGE = "\x00\x00\x0c\x06\x24\x90\xff\xf0\x49\x20\xff\xf0\x92\x40\xff\xf0"; | ||
|
||
public function testIdentify() { | ||
$codec = new WbmpCodec(); | ||
$this -> assertEquals("wbmp", $codec -> identify(self::WBMP_IMAGE)); | ||
$this -> assertEquals("", $codec -> identify("HELLO")); | ||
} | ||
|
||
public function testDecode() { | ||
$codec = new WbmpCodec(); | ||
$image = $codec -> decode(self::WBMP_IMAGE) -> toBlackAndWhite(); | ||
$this -> assertEquals(12, $image -> getWidth()); | ||
$this -> assertEquals(6, $image -> getHeight()); | ||
$content = "▀▀ ▀▀ ▀▀ ▀▀ \n" . | ||
"▀ ▀▀ ▀▀ ▀▀ ▀\n" . | ||
" ▀▀ ▀▀ ▀▀ ▀▀\n"; | ||
$this -> assertEquals($content, $image -> toString()); | ||
} | ||
|
||
public function testEncode() { | ||
// Raster representation is inverse to WBMP format. | ||
$image = BlackAndWhiteRasterImage::create(12, 6, [0xdb, 0x6f, 0x00, 0x0f, 0xb6, 0xdf, 0x00, 0x0f, 0x6d, 0xbf, 0x00, 0x0f]); | ||
$codec = new WbmpCodec(); | ||
$data = $codec -> encode($image, "wbmp"); | ||
$this -> assertEquals(self::WBMP_IMAGE, $data); | ||
} | ||
|
||
public function testReadOneByte() { | ||
$data = DataBlobInputStream::fromBlob("\x60"); | ||
$codec = new WbmpCodec(); | ||
$val = $codec -> readInt($data); | ||
$this -> assertEquals(0x60, $val); | ||
} | ||
|
||
public function testReadMultibyte() { | ||
$data = DataBlobInputStream::fromBlob("\x81\x20"); | ||
$codec = new WbmpCodec(); | ||
$val = $codec -> readInt($data); | ||
$this -> assertEquals(0xA0, $val); | ||
} | ||
|
||
public function testReadMax() { | ||
$data = DataBlobInputStream::fromBlob("\xFF\xFF\xFF\x7F\x00"); // Final byte not used | ||
$codec = new WbmpCodec(); | ||
$val = $codec -> readInt($data); | ||
$this -> assertEquals(268435455, $val); | ||
} | ||
|
||
public function testReadMultibyteOverflow() { | ||
// Appears to be no limit in WBMP to image dimensions, but we stop reading the multibyte-ints after 28 bits. | ||
$this -> expectException(Exception::class); | ||
$data = DataBlobInputStream::fromBlob("\xFF\xFF\xFF\x80\x00"); // (value in testMax()) + 1 | ||
$codec = new WbmpCodec(); | ||
$codec -> readInt($data); | ||
} | ||
|
||
public function testWriteOneByte() { | ||
$codec = new WbmpCodec(); | ||
$val = $codec -> writeInt(0x60); | ||
$this -> assertEquals("\x60", $val); | ||
} | ||
|
||
public function testWriteMultibyte() { | ||
$codec = new WbmpCodec(); | ||
$val = $codec -> writeInt(0xA0); | ||
$this -> assertEquals("\x81\x20", $val); | ||
} | ||
|
||
public function testWriteMax() { | ||
$codec = new WbmpCodec(); | ||
$val = $codec -> writeInt(268435455); | ||
$this -> assertEquals("\xFF\xFF\xFF\x7F", $val); | ||
|
||
} | ||
|
||
public function testWriteMultibyteOverflow() { | ||
$this -> expectException(Exception::class); | ||
$codec = new WbmpCodec(); | ||
$val = $codec -> writeInt(268435456); // As testWriteMax(), +1 | ||
} | ||
} |