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#43 from mike42/feature/35-bmp
Add BMP file reader
- Loading branch information
Showing
98 changed files
with
870 additions
and
1 deletion.
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,106 @@ | ||
<?php | ||
namespace Mike42\GfxPhp\Codec\Bmp; | ||
|
||
use Exception; | ||
use Mike42\GfxPhp\Codec\Common\DataInputStream; | ||
use Mike42\GfxPhp\RasterImage; | ||
use Mike42\GfxPhp\RgbRasterImage; | ||
|
||
class BmpFile | ||
{ | ||
const BMP_SIGNATURE = "BM"; | ||
|
||
private $fileHeader; | ||
private $infoHeader; | ||
private $uncompressedData; | ||
|
||
public function __construct(BmpFileHeader $fileHeader, BmpInfoHeader $infoHeader, array $data) | ||
{ | ||
$this -> fileHeader = $fileHeader; | ||
$this -> infoHeader = $infoHeader; | ||
$this -> uncompressedData = $data; | ||
} | ||
|
||
public static function fromBinary(DataInputStream $data) : BmpFile | ||
{ | ||
// Read two different headers | ||
$fileHeader = BmpFileHeader::fromBinary($data); | ||
$infoHeader = BmpInfoHeader::fromBinary($data); | ||
if ($infoHeader -> bpp != 0 && | ||
$infoHeader -> bpp != 1 && | ||
$infoHeader -> bpp != 4 && | ||
$infoHeader -> bpp != 8 && | ||
$infoHeader -> bpp != 16 && | ||
$infoHeader -> bpp != 24 && | ||
$infoHeader -> bpp != 32) { | ||
throw new Exception("Bit depth " . $infoHeader -> bpp . " not valid."); | ||
} else if ($infoHeader -> bpp != 24) { | ||
// Fail early to give a clearer error for the things which aren't tested yet | ||
throw new Exception("Bit depth " . $infoHeader -> bpp . " not implemented."); | ||
} | ||
// Skip color table (allowed in a true color image, but not useful) | ||
if ($infoHeader -> colors > 0) { | ||
// for non-truecolor images, 0 will mean 2^bpp. | ||
// Size of each entry may also be variable | ||
$data -> read($infoHeader -> colors * 4); | ||
} | ||
// Determine compressed & uncompressed size | ||
$rowSizeBytes = intdiv(($infoHeader -> bpp * $infoHeader -> width + 31), 32) * 4; | ||
$uncompressedImgSizeBytes = $rowSizeBytes * $infoHeader -> height; | ||
if ($infoHeader -> compression == BmpInfoHeader::B1_RGB) { | ||
$compressedImgSizeBytes = $uncompressedImgSizeBytes; | ||
} else { | ||
$compressedImgSizeBytes = $infoHeader -> compressedSize; | ||
} | ||
$compressedImgData = $data -> read($compressedImgSizeBytes); | ||
// De-compress if necessary | ||
switch ($infoHeader -> compression) { | ||
case BmpInfoHeader::B1_RGB: | ||
$uncompressedImgData = $compressedImgData; | ||
break; | ||
case BmpInfoHeader::B1_RLE8: | ||
case BmpInfoHeader::B1_RLE4: | ||
case BmpInfoHeader::B1_BITFILEDS: | ||
case BmpInfoHeader::B1_JPEG: | ||
case BmpInfoHeader::B1_PNG: | ||
case BmpInfoHeader::B1_ALPHABITFIELDS: | ||
case BmpInfoHeader::B1_CMYK: | ||
case BmpInfoHeader::B1_CMYKRLE8: | ||
case BmpInfoHeader::B1_CMYKRLE4: | ||
throw new Exception("Compression method not implemented"); | ||
default: | ||
throw new Exception("Bad compression method"); | ||
} | ||
// Account for padding, row order | ||
$paddedLines = str_split($uncompressedImgData, $rowSizeBytes); | ||
$dataLines = []; | ||
$rowDataBytes = intdiv($infoHeader -> bpp * $infoHeader -> width + 7, 8); // Excludes padding bytes | ||
for ($i = count($paddedLines) - 1; $i >= 0; $i--) { // Iterate lines backwards | ||
$dataLines[] = substr($paddedLines[$i], 0, $rowDataBytes); | ||
} | ||
$uncompressedImgData = implode("", $dataLines); | ||
// Account for RGB vs BGR in file format | ||
if ($infoHeader -> bpp == 24) { | ||
$pixels = str_split($uncompressedImgData, 3); | ||
array_walk($pixels, ["\\Mike42\\GfxPhp\\Codec\\Bmp\\BmpFile", "transformRevString"]); | ||
$uncompressedImgData = implode("", $pixels); | ||
} | ||
// Convert to array of numbers 0-255. | ||
$dataArray = array_values(unpack("c*", $uncompressedImgData)); | ||
return new BmpFile($fileHeader, $infoHeader, $dataArray); | ||
} | ||
|
||
public function toRasterImage() : RasterImage | ||
{ | ||
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 transformRevString(&$item, $key) | ||
{ | ||
// Convert RGB to BGR | ||
$item = strrev($item); | ||
} | ||
} |
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,31 @@ | ||
<?php | ||
|
||
|
||
namespace Mike42\GfxPhp\Codec\Bmp; | ||
|
||
use Exception; | ||
use Mike42\GfxPhp\Codec\Common\DataInputStream; | ||
|
||
class BmpFileHeader | ||
{ | ||
public $offset; | ||
public $size; | ||
|
||
public function __construct(string $fileType, int $size, int $offset) | ||
{ | ||
$this -> fileType = $fileType; | ||
$this -> size = $size; | ||
$this -> offset = $offset; | ||
} | ||
|
||
public static function fromBinary(DataInputStream $data) : BmpFileHeader | ||
{ | ||
$fileType = $data->read(2); | ||
if (array_search($fileType, ["BM", "BA", "CI", "CP", "IC", "PT", "OS"]) === false) { | ||
throw new Exception("Not a bitmap image"); | ||
} | ||
$fileHeaderData = $data->read(12); | ||
$fields = unpack("Vsize/vreserved1/vreserved2/Voffset", $fileHeaderData); | ||
return new BmpFileHeader($fileType, $fields['size'], $fields['offset']); | ||
} | ||
} |
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,90 @@ | ||
<?php | ||
namespace Mike42\GfxPhp\Codec\Bmp; | ||
|
||
use Exception; | ||
use Mike42\GfxPhp\Codec\Common\DataInputStream; | ||
|
||
class BmpInfoHeader | ||
{ | ||
const BITMAPCOREHEADER_SIZE = 12; | ||
const OS21XBITMAPHEADER_SIZE = 12; | ||
const BITMAPINFOHEADER_SIZE = 40; | ||
|
||
const B1_RGB = 0; | ||
const B1_RLE8 = 1; | ||
const B1_RLE4 = 2; | ||
const B1_BITFILEDS = 3; | ||
const B1_JPEG = 4; | ||
const B1_PNG = 5; | ||
const B1_ALPHABITFIELDS = 6; | ||
const B1_CMYK = 11; | ||
const B1_CMYKRLE8 = 12; | ||
const B1_CMYKRLE4 = 13; | ||
|
||
public $bpp; | ||
public $colors; | ||
public $compressedSize; | ||
public $compression; | ||
public $headerSize; | ||
public $height; | ||
public $hprizontalRes; | ||
public $importantColors; | ||
public $planes; | ||
public $verticalRes; | ||
public $width; | ||
|
||
public function __construct(int $headerSize, int $width, int $height, int $planes, int $bpp, int $compression = 0, int $compressedSize = 0, int $horizontalRes = 0, int $verticalRes = 0, int $colors = 0, int $importantColors = 0) | ||
{ | ||
$this -> headerSize = $headerSize; | ||
$this -> width = $width; | ||
$this -> height = $height; | ||
$this -> planes = $planes; | ||
$this -> bpp = $bpp; | ||
$this -> compression = $compression; | ||
$this -> compressedSize = $compressedSize; | ||
$this -> hprizontalRes = $horizontalRes; | ||
$this -> verticalRes = $verticalRes; | ||
$this -> colors = $colors; | ||
$this -> importantColors = $importantColors; | ||
} | ||
|
||
public static function fromBinary(DataInputStream $data) : BmpInfoHeader | ||
{ | ||
$infoHeaderSizeData = $data -> read(4); | ||
$infoHeaderSize = unpack("V", $infoHeaderSizeData)[1]; | ||
switch ($infoHeaderSize) { | ||
case self::BITMAPCOREHEADER_SIZE: | ||
return self::readCoreHeader($data); | ||
case 64: | ||
throw new Exception("OS22XBITMAPHEADER not implemented"); | ||
case 16: | ||
throw new Exception("OS22XBITMAPHEADER not implemented"); | ||
case self::BITMAPINFOHEADER_SIZE: | ||
return self::readBitmapInfoHeader($data); | ||
case 52: | ||
throw new Exception("BITMAPV2INFOHEADER not implemented"); | ||
case 56: | ||
throw new Exception("BITMAPV3INFOHEADER not implemented"); | ||
case 108: | ||
throw new Exception("BITMAPV4HEADER not implemented"); | ||
case 124: | ||
throw new Exception("BITMAPV5HEADER not implemented"); | ||
default: | ||
throw new Exception("Info header size " . $infoHeaderSize . " is not supported."); | ||
} | ||
} | ||
|
||
private static function readCoreHeader(DataInputStream $data) : BmpInfoHeader | ||
{ | ||
$infoData = $data -> read(self::BITMAPCOREHEADER_SIZE - 4); | ||
$fields = unpack("vwidth/vheight/vplanes/vbpp", $infoData); | ||
return new BmpInfoHeader(self::BITMAPCOREHEADER_SIZE, $fields['width'], $fields['height'], $fields['planes'], $fields['bpp']); | ||
} | ||
|
||
private static function readBitmapInfoHeader(DataInputStream $data) : BmpInfoHeader | ||
{ | ||
$infoData = $data -> read(self::BITMAPINFOHEADER_SIZE - 4); | ||
$fields = unpack("Vwidth/Vheight/vplanes/vbpp/Vcompression/VcompressedSize/VhorizontalRes/VverticalRes/Vcolors/VimportantColors", $infoData); | ||
return new BmpInfoHeader(self::BITMAPINFOHEADER_SIZE, $fields['width'], $fields['height'], $fields['planes'], $fields['bpp'], $fields['compression'], $fields['compressedSize'], $fields['horizontalRes'], $fields['verticalRes'], $fields['colors'], $fields['importantColors']); | ||
} | ||
} |
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
Oops, something went wrong.