JPEG (Joint Photographic Experts Group) File Interchange Format: Perl 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.

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

Jpeg.pm

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

use strict;
use warnings;
use IO::KaitaiStruct 0.009_000;
use Encode;
use Exif;

########################################################################
package Jpeg;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

our $COMPONENT_ID_Y = 1;
our $COMPONENT_ID_CB = 2;
our $COMPONENT_ID_CR = 3;
our $COMPONENT_ID_I = 4;
our $COMPONENT_ID_Q = 5;

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{segments} = ();
    while (!$self->{_io}->is_eof()) {
        push @{$self->{segments}}, Jpeg::Segment->new($self->{_io}, $self, $self->{_root});
    }
}

sub segments {
    my ($self) = @_;
    return $self->{segments};
}

########################################################################
package Jpeg::Segment;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

our $MARKER_ENUM_TEM = 1;
our $MARKER_ENUM_SOF0 = 192;
our $MARKER_ENUM_SOF1 = 193;
our $MARKER_ENUM_SOF2 = 194;
our $MARKER_ENUM_SOF3 = 195;
our $MARKER_ENUM_DHT = 196;
our $MARKER_ENUM_SOF5 = 197;
our $MARKER_ENUM_SOF6 = 198;
our $MARKER_ENUM_SOF7 = 199;
our $MARKER_ENUM_SOI = 216;
our $MARKER_ENUM_EOI = 217;
our $MARKER_ENUM_SOS = 218;
our $MARKER_ENUM_DQT = 219;
our $MARKER_ENUM_DNL = 220;
our $MARKER_ENUM_DRI = 221;
our $MARKER_ENUM_DHP = 222;
our $MARKER_ENUM_APP0 = 224;
our $MARKER_ENUM_APP1 = 225;
our $MARKER_ENUM_APP2 = 226;
our $MARKER_ENUM_APP3 = 227;
our $MARKER_ENUM_APP4 = 228;
our $MARKER_ENUM_APP5 = 229;
our $MARKER_ENUM_APP6 = 230;
our $MARKER_ENUM_APP7 = 231;
our $MARKER_ENUM_APP8 = 232;
our $MARKER_ENUM_APP9 = 233;
our $MARKER_ENUM_APP10 = 234;
our $MARKER_ENUM_APP11 = 235;
our $MARKER_ENUM_APP12 = 236;
our $MARKER_ENUM_APP13 = 237;
our $MARKER_ENUM_APP14 = 238;
our $MARKER_ENUM_APP15 = 239;
our $MARKER_ENUM_COM = 254;

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{magic} = $self->{_io}->read_bytes(1);
    $self->{marker} = $self->{_io}->read_u1();
    if ( (($self->marker() != $Jpeg::Segment::MARKER_ENUM_SOI) && ($self->marker() != $Jpeg::Segment::MARKER_ENUM_EOI)) ) {
        $self->{length} = $self->{_io}->read_u2be();
    }
    if ( (($self->marker() != $Jpeg::Segment::MARKER_ENUM_SOI) && ($self->marker() != $Jpeg::Segment::MARKER_ENUM_EOI)) ) {
        my $_on = $self->marker();
        if ($_on == $Jpeg::Segment::MARKER_ENUM_APP1) {
            $self->{_raw_data} = $self->{_io}->read_bytes(($self->length() - 2));
            my $io__raw_data = IO::KaitaiStruct::Stream->new($self->{_raw_data});
            $self->{data} = Jpeg::SegmentApp1->new($io__raw_data, $self, $self->{_root});
        }
        elsif ($_on == $Jpeg::Segment::MARKER_ENUM_APP0) {
            $self->{_raw_data} = $self->{_io}->read_bytes(($self->length() - 2));
            my $io__raw_data = IO::KaitaiStruct::Stream->new($self->{_raw_data});
            $self->{data} = Jpeg::SegmentApp0->new($io__raw_data, $self, $self->{_root});
        }
        elsif ($_on == $Jpeg::Segment::MARKER_ENUM_SOF0) {
            $self->{_raw_data} = $self->{_io}->read_bytes(($self->length() - 2));
            my $io__raw_data = IO::KaitaiStruct::Stream->new($self->{_raw_data});
            $self->{data} = Jpeg::SegmentSof0->new($io__raw_data, $self, $self->{_root});
        }
        elsif ($_on == $Jpeg::Segment::MARKER_ENUM_SOS) {
            $self->{_raw_data} = $self->{_io}->read_bytes(($self->length() - 2));
            my $io__raw_data = IO::KaitaiStruct::Stream->new($self->{_raw_data});
            $self->{data} = Jpeg::SegmentSos->new($io__raw_data, $self, $self->{_root});
        }
        else {
            $self->{data} = $self->{_io}->read_bytes(($self->length() - 2));
        }
    }
    if ($self->marker() == $Jpeg::Segment::MARKER_ENUM_SOS) {
        $self->{image_data} = $self->{_io}->read_bytes_full();
    }
}

sub magic {
    my ($self) = @_;
    return $self->{magic};
}

sub marker {
    my ($self) = @_;
    return $self->{marker};
}

sub length {
    my ($self) = @_;
    return $self->{length};
}

sub data {
    my ($self) = @_;
    return $self->{data};
}

sub image_data {
    my ($self) = @_;
    return $self->{image_data};
}

sub _raw_data {
    my ($self) = @_;
    return $self->{_raw_data};
}

########################################################################
package Jpeg::SegmentSos;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{num_components} = $self->{_io}->read_u1();
    $self->{components} = ();
    my $n_components = $self->num_components();
    for (my $i = 0; $i < $n_components; $i++) {
        push @{$self->{components}}, Jpeg::SegmentSos::Component->new($self->{_io}, $self, $self->{_root});
    }
    $self->{start_spectral_selection} = $self->{_io}->read_u1();
    $self->{end_spectral} = $self->{_io}->read_u1();
    $self->{appr_bit_pos} = $self->{_io}->read_u1();
}

sub num_components {
    my ($self) = @_;
    return $self->{num_components};
}

sub components {
    my ($self) = @_;
    return $self->{components};
}

sub start_spectral_selection {
    my ($self) = @_;
    return $self->{start_spectral_selection};
}

sub end_spectral {
    my ($self) = @_;
    return $self->{end_spectral};
}

sub appr_bit_pos {
    my ($self) = @_;
    return $self->{appr_bit_pos};
}

########################################################################
package Jpeg::SegmentSos::Component;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{id} = $self->{_io}->read_u1();
    $self->{huffman_table} = $self->{_io}->read_u1();
}

sub id {
    my ($self) = @_;
    return $self->{id};
}

sub huffman_table {
    my ($self) = @_;
    return $self->{huffman_table};
}

########################################################################
package Jpeg::SegmentApp1;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{magic} = Encode::decode("ASCII", $self->{_io}->read_bytes_term(0, 0, 1, 1));
    my $_on = $self->magic();
    if ($_on eq "Exif") {
        $self->{body} = Jpeg::ExifInJpeg->new($self->{_io}, $self, $self->{_root});
    }
}

sub magic {
    my ($self) = @_;
    return $self->{magic};
}

sub body {
    my ($self) = @_;
    return $self->{body};
}

########################################################################
package Jpeg::SegmentSof0;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{bits_per_sample} = $self->{_io}->read_u1();
    $self->{image_height} = $self->{_io}->read_u2be();
    $self->{image_width} = $self->{_io}->read_u2be();
    $self->{num_components} = $self->{_io}->read_u1();
    $self->{components} = ();
    my $n_components = $self->num_components();
    for (my $i = 0; $i < $n_components; $i++) {
        push @{$self->{components}}, Jpeg::SegmentSof0::Component->new($self->{_io}, $self, $self->{_root});
    }
}

sub bits_per_sample {
    my ($self) = @_;
    return $self->{bits_per_sample};
}

sub image_height {
    my ($self) = @_;
    return $self->{image_height};
}

sub image_width {
    my ($self) = @_;
    return $self->{image_width};
}

sub num_components {
    my ($self) = @_;
    return $self->{num_components};
}

sub components {
    my ($self) = @_;
    return $self->{components};
}

########################################################################
package Jpeg::SegmentSof0::Component;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{id} = $self->{_io}->read_u1();
    $self->{sampling_factors} = $self->{_io}->read_u1();
    $self->{quantization_table_id} = $self->{_io}->read_u1();
}

sub sampling_x {
    my ($self) = @_;
    return $self->{sampling_x} if ($self->{sampling_x});
    $self->{sampling_x} = (($self->sampling_factors() & 240) >> 4);
    return $self->{sampling_x};
}

sub sampling_y {
    my ($self) = @_;
    return $self->{sampling_y} if ($self->{sampling_y});
    $self->{sampling_y} = ($self->sampling_factors() & 15);
    return $self->{sampling_y};
}

sub id {
    my ($self) = @_;
    return $self->{id};
}

sub sampling_factors {
    my ($self) = @_;
    return $self->{sampling_factors};
}

sub quantization_table_id {
    my ($self) = @_;
    return $self->{quantization_table_id};
}

########################################################################
package Jpeg::ExifInJpeg;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{extra_zero} = $self->{_io}->read_bytes(1);
    $self->{_raw_data} = $self->{_io}->read_bytes_full();
    my $io__raw_data = IO::KaitaiStruct::Stream->new($self->{_raw_data});
    $self->{data} = Exif->new($io__raw_data);
}

sub extra_zero {
    my ($self) = @_;
    return $self->{extra_zero};
}

sub data {
    my ($self) = @_;
    return $self->{data};
}

sub _raw_data {
    my ($self) = @_;
    return $self->{_raw_data};
}

########################################################################
package Jpeg::SegmentApp0;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

our $DENSITY_UNIT_NO_UNITS = 0;
our $DENSITY_UNIT_PIXELS_PER_INCH = 1;
our $DENSITY_UNIT_PIXELS_PER_CM = 2;

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{magic} = Encode::decode("ASCII", $self->{_io}->read_bytes(5));
    $self->{version_major} = $self->{_io}->read_u1();
    $self->{version_minor} = $self->{_io}->read_u1();
    $self->{density_units} = $self->{_io}->read_u1();
    $self->{density_x} = $self->{_io}->read_u2be();
    $self->{density_y} = $self->{_io}->read_u2be();
    $self->{thumbnail_x} = $self->{_io}->read_u1();
    $self->{thumbnail_y} = $self->{_io}->read_u1();
    $self->{thumbnail} = $self->{_io}->read_bytes((($self->thumbnail_x() * $self->thumbnail_y()) * 3));
}

sub magic {
    my ($self) = @_;
    return $self->{magic};
}

sub version_major {
    my ($self) = @_;
    return $self->{version_major};
}

sub version_minor {
    my ($self) = @_;
    return $self->{version_minor};
}

sub density_units {
    my ($self) = @_;
    return $self->{density_units};
}

sub density_x {
    my ($self) = @_;
    return $self->{density_x};
}

sub density_y {
    my ($self) = @_;
    return $self->{density_y};
}

sub thumbnail_x {
    my ($self) = @_;
    return $self->{thumbnail_x};
}

sub thumbnail_y {
    my ($self) = @_;
    return $self->{thumbnail_y};
}

sub thumbnail {
    my ($self) = @_;
    return $self->{thumbnail};
}

1;