JPEG (Joint Photographic Experts Group) File Interchange Format: PHP parsing library

JPEG File Interchange Format, or JFIF, or, more colloquially known as just "JPEG" or "JPG", is a popular 2D bitmap image file format, offering lossy compression which works reasonably well with photographic images.

Format is organized as a container format, serving multiple "segments", each starting with a magic and a marker. JFIF standard dictates order and mandatory apperance of segments:

  • SOI
  • APP0 (with JFIF magic)
  • APP0 (with JFXX magic, optional)
  • everything else
  • SOS
  • JPEG-compressed stream
  • EOI

File extension

["jpg", "jpeg", "jpe", "jif", "jfif", "jfi"]

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of JPEG (Joint Photographic Experts Group) File Interchange Format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

PHP source code to parse JPEG (Joint Photographic Experts Group) File Interchange Format

Jpeg.php

<?php
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

/**
 * JPEG File Interchange Format, or JFIF, or, more colloquially known
 * as just "JPEG" or "JPG", is a popular 2D bitmap image file format,
 * offering lossy compression which works reasonably well with
 * photographic images.
 * 
 * Format is organized as a container format, serving multiple
 * "segments", each starting with a magic and a marker. JFIF standard
 * dictates order and mandatory apperance of segments:
 * 
 * * SOI
 * * APP0 (with JFIF magic)
 * * APP0 (with JFXX magic, optional)
 * * everything else
 * * SOS
 * * JPEG-compressed stream
 * * EOI
 */

namespace {
    class Jpeg extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Kaitai\Struct\Struct $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_segments = [];
            $i = 0;
            while (!$this->_io->isEof()) {
                $this->_m_segments[] = new \Jpeg\Segment($this->_io, $this, $this->_root);
                $i++;
            }
        }
        protected $_m_segments;
        public function segments() { return $this->_m_segments; }
    }
}

namespace Jpeg {
    class Segment extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_magic = $this->_io->readBytes(1);
            if (!($this->magic() == "\xFF")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\xFF", $this->magic(), $this->_io(), "/types/segment/seq/0");
            }
            $this->_m_marker = $this->_io->readU1();
            if ( (($this->marker() != \Jpeg\Segment\MarkerEnum::SOI) && ($this->marker() != \Jpeg\Segment\MarkerEnum::EOI)) ) {
                $this->_m_length = $this->_io->readU2be();
            }
            if ( (($this->marker() != \Jpeg\Segment\MarkerEnum::SOI) && ($this->marker() != \Jpeg\Segment\MarkerEnum::EOI)) ) {
                switch ($this->marker()) {
                    case \Jpeg\Segment\MarkerEnum::APP1:
                        $this->_m__raw_data = $this->_io->readBytes(($this->length() - 2));
                        $_io__raw_data = new \Kaitai\Struct\Stream($this->_m__raw_data);
                        $this->_m_data = new \Jpeg\SegmentApp1($_io__raw_data, $this, $this->_root);
                        break;
                    case \Jpeg\Segment\MarkerEnum::APP0:
                        $this->_m__raw_data = $this->_io->readBytes(($this->length() - 2));
                        $_io__raw_data = new \Kaitai\Struct\Stream($this->_m__raw_data);
                        $this->_m_data = new \Jpeg\SegmentApp0($_io__raw_data, $this, $this->_root);
                        break;
                    case \Jpeg\Segment\MarkerEnum::SOF0:
                        $this->_m__raw_data = $this->_io->readBytes(($this->length() - 2));
                        $_io__raw_data = new \Kaitai\Struct\Stream($this->_m__raw_data);
                        $this->_m_data = new \Jpeg\SegmentSof0($_io__raw_data, $this, $this->_root);
                        break;
                    case \Jpeg\Segment\MarkerEnum::SOS:
                        $this->_m__raw_data = $this->_io->readBytes(($this->length() - 2));
                        $_io__raw_data = new \Kaitai\Struct\Stream($this->_m__raw_data);
                        $this->_m_data = new \Jpeg\SegmentSos($_io__raw_data, $this, $this->_root);
                        break;
                    default:
                        $this->_m_data = $this->_io->readBytes(($this->length() - 2));
                        break;
                }
            }
            if ($this->marker() == \Jpeg\Segment\MarkerEnum::SOS) {
                $this->_m_imageData = $this->_io->readBytesFull();
            }
        }
        protected $_m_magic;
        protected $_m_marker;
        protected $_m_length;
        protected $_m_data;
        protected $_m_imageData;
        protected $_m__raw_data;
        public function magic() { return $this->_m_magic; }
        public function marker() { return $this->_m_marker; }
        public function length() { return $this->_m_length; }
        public function data() { return $this->_m_data; }
        public function imageData() { return $this->_m_imageData; }
        public function _raw_data() { return $this->_m__raw_data; }
    }
}

namespace Jpeg\Segment {
    class MarkerEnum {
        const TEM = 1;
        const SOF0 = 192;
        const SOF1 = 193;
        const SOF2 = 194;
        const SOF3 = 195;
        const DHT = 196;
        const SOF5 = 197;
        const SOF6 = 198;
        const SOF7 = 199;
        const SOI = 216;
        const EOI = 217;
        const SOS = 218;
        const DQT = 219;
        const DNL = 220;
        const DRI = 221;
        const DHP = 222;
        const APP0 = 224;
        const APP1 = 225;
        const APP2 = 226;
        const APP3 = 227;
        const APP4 = 228;
        const APP5 = 229;
        const APP6 = 230;
        const APP7 = 231;
        const APP8 = 232;
        const APP9 = 233;
        const APP10 = 234;
        const APP11 = 235;
        const APP12 = 236;
        const APP13 = 237;
        const APP14 = 238;
        const APP15 = 239;
        const COM = 254;
    }
}

namespace Jpeg {
    class SegmentSos extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg\Segment $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_numComponents = $this->_io->readU1();
            $this->_m_components = [];
            $n = $this->numComponents();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_components[] = new \Jpeg\SegmentSos\Component($this->_io, $this, $this->_root);
            }
            $this->_m_startSpectralSelection = $this->_io->readU1();
            $this->_m_endSpectral = $this->_io->readU1();
            $this->_m_apprBitPos = $this->_io->readU1();
        }
        protected $_m_numComponents;
        protected $_m_components;
        protected $_m_startSpectralSelection;
        protected $_m_endSpectral;
        protected $_m_apprBitPos;

        /**
         * Number of components in scan
         */
        public function numComponents() { return $this->_m_numComponents; }

        /**
         * Scan components specification
         */
        public function components() { return $this->_m_components; }

        /**
         * Start of spectral selection or predictor selection
         */
        public function startSpectralSelection() { return $this->_m_startSpectralSelection; }

        /**
         * End of spectral selection
         */
        public function endSpectral() { return $this->_m_endSpectral; }

        /**
         * Successive approximation bit position high + Successive approximation bit position low or point transform
         */
        public function apprBitPos() { return $this->_m_apprBitPos; }
    }
}

namespace Jpeg\SegmentSos {
    class Component extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg\SegmentSos $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_id = $this->_io->readU1();
            $this->_m_huffmanTable = $this->_io->readU1();
        }
        protected $_m_id;
        protected $_m_huffmanTable;

        /**
         * Scan component selector
         */
        public function id() { return $this->_m_id; }
        public function huffmanTable() { return $this->_m_huffmanTable; }
    }
}

namespace Jpeg {
    class SegmentApp1 extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg\Segment $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_magic = \Kaitai\Struct\Stream::bytesToStr($this->_io->readBytesTerm(0, false, true, true), "ASCII");
            switch ($this->magic()) {
                case "Exif":
                    $this->_m_body = new \Jpeg\ExifInJpeg($this->_io, $this, $this->_root);
                    break;
            }
        }
        protected $_m_magic;
        protected $_m_body;
        public function magic() { return $this->_m_magic; }
        public function body() { return $this->_m_body; }
    }
}

namespace Jpeg {
    class SegmentSof0 extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg\Segment $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_bitsPerSample = $this->_io->readU1();
            $this->_m_imageHeight = $this->_io->readU2be();
            $this->_m_imageWidth = $this->_io->readU2be();
            $this->_m_numComponents = $this->_io->readU1();
            $this->_m_components = [];
            $n = $this->numComponents();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_components[] = new \Jpeg\SegmentSof0\Component($this->_io, $this, $this->_root);
            }
        }
        protected $_m_bitsPerSample;
        protected $_m_imageHeight;
        protected $_m_imageWidth;
        protected $_m_numComponents;
        protected $_m_components;
        public function bitsPerSample() { return $this->_m_bitsPerSample; }
        public function imageHeight() { return $this->_m_imageHeight; }
        public function imageWidth() { return $this->_m_imageWidth; }
        public function numComponents() { return $this->_m_numComponents; }
        public function components() { return $this->_m_components; }
    }
}

namespace Jpeg\SegmentSof0 {
    class Component extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg\SegmentSof0 $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_id = $this->_io->readU1();
            $this->_m_samplingFactors = $this->_io->readU1();
            $this->_m_quantizationTableId = $this->_io->readU1();
        }
        protected $_m_samplingX;
        public function samplingX() {
            if ($this->_m_samplingX !== null)
                return $this->_m_samplingX;
            $this->_m_samplingX = (($this->samplingFactors() & 240) >> 4);
            return $this->_m_samplingX;
        }
        protected $_m_samplingY;
        public function samplingY() {
            if ($this->_m_samplingY !== null)
                return $this->_m_samplingY;
            $this->_m_samplingY = ($this->samplingFactors() & 15);
            return $this->_m_samplingY;
        }
        protected $_m_id;
        protected $_m_samplingFactors;
        protected $_m_quantizationTableId;

        /**
         * Component selector
         */
        public function id() { return $this->_m_id; }
        public function samplingFactors() { return $this->_m_samplingFactors; }
        public function quantizationTableId() { return $this->_m_quantizationTableId; }
    }
}

namespace Jpeg {
    class ExifInJpeg extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg\SegmentApp1 $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_extraZero = $this->_io->readBytes(1);
            if (!($this->extraZero() == "\x00")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\x00", $this->extraZero(), $this->_io(), "/types/exif_in_jpeg/seq/0");
            }
            $this->_m__raw_data = $this->_io->readBytesFull();
            $_io__raw_data = new \Kaitai\Struct\Stream($this->_m__raw_data);
            $this->_m_data = new \Exif($_io__raw_data);
        }
        protected $_m_extraZero;
        protected $_m_data;
        protected $_m__raw_data;
        public function extraZero() { return $this->_m_extraZero; }
        public function data() { return $this->_m_data; }
        public function _raw_data() { return $this->_m__raw_data; }
    }
}

namespace Jpeg {
    class SegmentApp0 extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Jpeg\Segment $_parent = null, \Jpeg $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_magic = \Kaitai\Struct\Stream::bytesToStr($this->_io->readBytes(5), "ASCII");
            $this->_m_versionMajor = $this->_io->readU1();
            $this->_m_versionMinor = $this->_io->readU1();
            $this->_m_densityUnits = $this->_io->readU1();
            $this->_m_densityX = $this->_io->readU2be();
            $this->_m_densityY = $this->_io->readU2be();
            $this->_m_thumbnailX = $this->_io->readU1();
            $this->_m_thumbnailY = $this->_io->readU1();
            $this->_m_thumbnail = $this->_io->readBytes((($this->thumbnailX() * $this->thumbnailY()) * 3));
        }
        protected $_m_magic;
        protected $_m_versionMajor;
        protected $_m_versionMinor;
        protected $_m_densityUnits;
        protected $_m_densityX;
        protected $_m_densityY;
        protected $_m_thumbnailX;
        protected $_m_thumbnailY;
        protected $_m_thumbnail;
        public function magic() { return $this->_m_magic; }
        public function versionMajor() { return $this->_m_versionMajor; }
        public function versionMinor() { return $this->_m_versionMinor; }
        public function densityUnits() { return $this->_m_densityUnits; }

        /**
         * Horizontal pixel density. Must not be zero.
         */
        public function densityX() { return $this->_m_densityX; }

        /**
         * Vertical pixel density. Must not be zero.
         */
        public function densityY() { return $this->_m_densityY; }

        /**
         * Horizontal pixel count of the following embedded RGB thumbnail. May be zero.
         */
        public function thumbnailX() { return $this->_m_thumbnailX; }

        /**
         * Vertical pixel count of the following embedded RGB thumbnail. May be zero.
         */
        public function thumbnailY() { return $this->_m_thumbnailY; }

        /**
         * Uncompressed 24 bit RGB (8 bits per color channel) raster thumbnail data in the order R0, G0, B0, ... Rn, Gn, Bn
         */
        public function thumbnail() { return $this->_m_thumbnail; }
    }
}

namespace Jpeg\SegmentApp0 {
    class DensityUnit {
        const NO_UNITS = 0;
        const PIXELS_PER_INCH = 1;
        const PIXELS_PER_CM = 2;
    }
}

namespace Jpeg {
    class ComponentId {
        const Y = 1;
        const CB = 2;
        const CR = 3;
        const I = 4;
        const Q = 5;
    }
}