gomoob/php-metadata-extractor

View on GitHub
src/main/php/Gomoob/MetadataExtractor/Imaging/Png/PngChunkType.php

Summary

Maintainability
A
25 mins
Test Coverage
<?php

/**
 * gomoob/php-metadata-extractor
 *
 * @copyright Copyright (c) 2016, GOMOOB SARL (http://gomoob.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE.md file)
 */
namespace Gomoob\MetadataExtractor\Imaging\Png;

use Gomoob\Java\Io\UnsupportedEncodingException;

use Gomoob\Java\Lang\IllegalArgumentException;

/**
 * @author Baptiste GAILLARD (baptiste.gaillard@gomoob.com)
 */
class PngChunkType
{
    private $identifiersAllowingMultiples = ['IDAT', 'sPLT', 'iTXt', 'tEXt', 'zTXt'];

    //
    // Standard critical chunks
    //
    /**
     * Denotes a critical {@link PngChunk} that contains basic information about the PNG image.
     * This must be the first chunk in the data sequence, and may only occur once.
     *
     * The format is:
     * * <b>pixel width</b> 4 bytes, unsigned and greater than zero
     * * <b>pixel height</b> 4 bytes, unsigned and greater than zero>
     * * <b>bit depth</b> 1 byte, number of bits per sample or per palette index (not per pixel)
     * * <b>color type</b> 1 byte, maps to {@link PngColorType} enum
     * * <b>compression method</b> 1 byte, currently only a value of zero (deflate/inflate) is in the standard
     * * <b>filter method</b> 1 byte, currently only a value of zero (adaptive filtering with five basic filter types)
     *   is in the standard</li>
     * * <b>interlace method</b> 1 byte, indicates the transmission order of image data, currently only 0 (no interlace)
     *   and 1 (Adam7 interlace) are in the standard</li>
     */
    public static $IHDR = null;

    /**
     * Denotes a critical {@link PngChunk} that contains palette entries.
     * This chunk should only appear for a {@link PngColorType} of <code>IndexedColor</code>,
     * and may only occur once in the PNG data sequence.
     * <p>
     * The chunk contains between one and 256 entries, each of three bytes:
     * <ul>
     *     <li><b>red</b> 1 byte</li>
     *     <li><b>green</b> 1 byte</li>
     *     <li><b>blue</b> 1 byte</li>
     * </ul>
     * The number of entries is determined by the chunk length. A chunk length indivisible by three is an error.
     */
    public static $PLTE = null;
    public static $IDAT = null;
    public static $IEND = null;
    
    //
    // Standard ancillary chunks
    //
    public static $cHRM = null;
    public static $gAMA = null;
    public static $iCCP = null;
    public static $sBIT = null;
    public static $sRGB = null;
    public static $bKGD = null;
    public static $hIST = null;
    public static $tRNS = null;
    public static $pHYs = null;
    public static $sPLT = null;
    public static $tIME = null;
    public static $iTXt = null;
    
    /**
     * Denotes an ancillary {@link PngChunk} that contains textual data, having first a keyword and then a value.
     * If multiple text data keywords are needed, then multiple chunks are included in the PNG data stream.
     * <p>
     * The format is:
     * <ul>
     *     <li><b>keyword</b> 1-79 bytes</li>
     *     <li><b>null separator</b> 1 byte (\0)</li>
     *     <li><b>text string</b> 0 or more bytes</li>
     * </ul>
     * Text is interpreted according to the Latin-1 character set [ISO-8859-1].
     * Newlines should be represented by a single linefeed character (0x9).
     */
    public static $tEXt = null;
    public static $zTXt = null;

    private $bytes;
    private $multipleAllowed;

    public function __construct($identifierOrBytes, $multipleAllowed = null)
    {
        if (is_string($identifierOrBytes)) {
            $this->multipleAllowed = $multipleAllowed;
            try {
                $splitted = str_split($identifierOrBytes);
                $bytes = [];

                foreach ($splitted as $s) {
                    $bytes[] = ord($s[0]);
                }
                $this->validateBytes($bytes);
                $this->bytes = $bytes;
            } catch (UnsupportedEncodingException $e) {
                throw new IllegalArgumentException("Unable to convert string code to bytes.");
            }
        } elseif (is_array($identifierOrBytes)) {
            $this->validateBytes($bytes);
            $this->bytes = $bytes;
            $this->multipleAllowed = in_array($this->getIdentifier(), $this->identifiersAllowingMultiples);
        }
    }
    
    private static function validateBytes($bytes)
    {
        if (count($bytes) != 4) {
            throw new IllegalArgumentException("PNG chunk type identifier must be four bytes in length");
        }
    
        foreach ($bytes as $b) {
            if (!static::isValidByte($b)) {
                throw new IllegalArgumentException("PNG chunk type identifier may only contain alphabet characters");
            }
        }
    }
    
    public function isCritical()
    {
        return $this->isUpperCase($this->bytes[0]);
    }
    
    public function isAncillary()
    {
        return !$this->isCritical();
    }
    
    public function isPrivate()
    {
        return $this->isUpperCase($this->bytes[1]);
    }
    
    public function isSafeToCopy()
    {
        return $this->isLowerCase($this->bytes[3]);
    }
    
    public function areMultipleAllowed()
    {
        return $this->multipleAllowed;
    }
    
    private static function isLowerCase($b)
    {
        return ($b & (1 << 5)) != 0;
    }
    
    private static function isUpperCase($b)
    {
        return ($b & (1 << 5)) == 0;
    }
    
    private static function isValidByte($b)
    {
        return ($b >= 65 && $b <= 90) || ($b >= 97 && $b <= 122);
    }
    
    public function getIdentifier()
    {
        try {
            $identifier = '';
            foreach ($this->bytes as $b) {
                $identifier .= chr($b);
            }
            
            return $identifier;
        } catch (UnsupportedEncodingException $e) {
            // The constructor should ensure that we're always able to encode the bytes in ASCII.
            // noinspection ConstantConditions
            assert(false);
            return "Invalid object instance";
        }
    }

    public function toString()
    {
        return $this->getIdentifier();
    }
    
    public function equals($o)
    {
        if ($this === $o) {
            return true;
        }
    
        if ($o === null || $this->getClass() !== $o->getClass()) {
            return false;
        }
    
        $that = $o;
    
        return Arrays.equals($this->bytes, $that->bytes);
    }
    
    public function hashCode()
    {
        return Arrays.hashCode($this->bytes);
    }
}

/* TODO: Will be fixed when static initializers will be added in PHP
 * 
 * ----------------------------------------------------------------------
 * FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
 * ----------------------------------------------------------------------
 * 1 | WARNING | A file should declare new symbols (classes, functions,
 *   |         | constants, etc.) and cause no other side effects, or
 *   |         | it should execute logic with side effects, but should
 *   |         | not do both. The first symbol is defined on line 16
 *   |         | and the first side effect is on line 212.
 * ----------------------------------------------------------------------
PngChunkType::$IHDR = new PngChunkType('IHDR');
PngChunkType::$PLTE = new PngChunkType("PLTE");
PngChunkType::$IDAT = new PngChunkType("IDAT", true);
PngChunkType::$IEND = new PngChunkType("IEND");
PngChunkType::$cHRM = new PngChunkType("cHRM");
PngChunkType::$gAMA = new PngChunkType("gAMA");
PngChunkType::$iCCP = new PngChunkType("iCCP");
PngChunkType::$sBIT = new PngChunkType("sBIT");
PngChunkType::$sRGB = new PngChunkType("sRGB");
PngChunkType::$bKGD = new PngChunkType("bKGD");
PngChunkType::$hIST = new PngChunkType("hIST");
PngChunkType::$tRNS = new PngChunkType("tRNS");
PngChunkType::$pHYs = new PngChunkType("pHYs");
PngChunkType::$sPLT = new PngChunkType("sPLT", true);
PngChunkType::$tIME = new PngChunkType("tIME");
PngChunkType::$iTXt = new PngChunkType("iTXt", true);
PngChunkType::$tEXt = new PngChunkType("tEXt", true);
PngChunkType::$zTXt = new PngChunkType("zTXt", true);
*/