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

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

jpeg.nim

import kaitai_struct_nim_runtime
import options
import exif

import "exif"
type
  Jpeg* = ref object of KaitaiStruct
    `segments`*: seq[Jpeg_Segment]
    `parent`*: KaitaiStruct
  Jpeg_ComponentId* = enum
    y = 1
    cb = 2
    cr = 3
    i = 4
    q = 5
  Jpeg_Segment* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `marker`*: Jpeg_Segment_MarkerEnum
    `length`*: uint16
    `data`*: KaitaiStruct
    `imageData`*: seq[byte]
    `parent`*: Jpeg
    `rawData`*: seq[byte]
  Jpeg_Segment_MarkerEnum* = enum
    tem = 1
    sof0 = 192
    sof1 = 193
    sof2 = 194
    sof3 = 195
    dht = 196
    sof5 = 197
    sof6 = 198
    sof7 = 199
    soi = 216
    eoi = 217
    sos = 218
    dqt = 219
    dnl = 220
    dri = 221
    dhp = 222
    app0 = 224
    app1 = 225
    app2 = 226
    app3 = 227
    app4 = 228
    app5 = 229
    app6 = 230
    app7 = 231
    app8 = 232
    app9 = 233
    app10 = 234
    app11 = 235
    app12 = 236
    app13 = 237
    app14 = 238
    app15 = 239
    com = 254
  Jpeg_SegmentSos* = ref object of KaitaiStruct
    `numComponents`*: uint8
    `components`*: seq[Jpeg_SegmentSos_Component]
    `startSpectralSelection`*: uint8
    `endSpectral`*: uint8
    `apprBitPos`*: uint8
    `parent`*: Jpeg_Segment
  Jpeg_SegmentSos_Component* = ref object of KaitaiStruct
    `id`*: Jpeg_ComponentId
    `huffmanTable`*: uint8
    `parent`*: Jpeg_SegmentSos
  Jpeg_SegmentApp1* = ref object of KaitaiStruct
    `magic`*: string
    `body`*: Jpeg_ExifInJpeg
    `parent`*: Jpeg_Segment
  Jpeg_SegmentSof0* = ref object of KaitaiStruct
    `bitsPerSample`*: uint8
    `imageHeight`*: uint16
    `imageWidth`*: uint16
    `numComponents`*: uint8
    `components`*: seq[Jpeg_SegmentSof0_Component]
    `parent`*: Jpeg_Segment
  Jpeg_SegmentSof0_Component* = ref object of KaitaiStruct
    `id`*: Jpeg_ComponentId
    `samplingFactors`*: uint8
    `quantizationTableId`*: uint8
    `parent`*: Jpeg_SegmentSof0
    `samplingXInst`: int
    `samplingXInstFlag`: bool
    `samplingYInst`: int
    `samplingYInstFlag`: bool
  Jpeg_ExifInJpeg* = ref object of KaitaiStruct
    `extraZero`*: seq[byte]
    `data`*: Exif
    `parent`*: Jpeg_SegmentApp1
    `rawData`*: seq[byte]
  Jpeg_SegmentApp0* = ref object of KaitaiStruct
    `magic`*: string
    `versionMajor`*: uint8
    `versionMinor`*: uint8
    `densityUnits`*: Jpeg_SegmentApp0_DensityUnit
    `densityX`*: uint16
    `densityY`*: uint16
    `thumbnailX`*: uint8
    `thumbnailY`*: uint8
    `thumbnail`*: seq[byte]
    `parent`*: Jpeg_Segment
  Jpeg_SegmentApp0_DensityUnit* = enum
    no_units = 0
    pixels_per_inch = 1
    pixels_per_cm = 2

proc read*(_: typedesc[Jpeg], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Jpeg
proc read*(_: typedesc[Jpeg_Segment], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg): Jpeg_Segment
proc read*(_: typedesc[Jpeg_SegmentSos], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentSos
proc read*(_: typedesc[Jpeg_SegmentSos_Component], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_SegmentSos): Jpeg_SegmentSos_Component
proc read*(_: typedesc[Jpeg_SegmentApp1], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentApp1
proc read*(_: typedesc[Jpeg_SegmentSof0], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentSof0
proc read*(_: typedesc[Jpeg_SegmentSof0_Component], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_SegmentSof0): Jpeg_SegmentSof0_Component
proc read*(_: typedesc[Jpeg_ExifInJpeg], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_SegmentApp1): Jpeg_ExifInJpeg
proc read*(_: typedesc[Jpeg_SegmentApp0], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentApp0

proc samplingX*(this: Jpeg_SegmentSof0_Component): int
proc samplingY*(this: Jpeg_SegmentSof0_Component): int


##[
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

]##
proc read*(_: typedesc[Jpeg], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Jpeg =
  template this: untyped = result
  this = new(Jpeg)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent

  block:
    var i: int
    while not this.io.isEof:
      let it = Jpeg_Segment.read(this.io, this.root, this)
      this.segments.add(it)
      inc i

proc fromFile*(_: typedesc[Jpeg], filename: string): Jpeg =
  Jpeg.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_Segment], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg): Jpeg_Segment =
  template this: untyped = result
  this = new(Jpeg_Segment)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = this.io.readBytes(int(1))
  this.magic = magicExpr
  let markerExpr = Jpeg_Segment_MarkerEnum(this.io.readU1())
  this.marker = markerExpr
  if  ((this.marker != jpeg.soi) and (this.marker != jpeg.eoi)) :
    let lengthExpr = this.io.readU2be()
    this.length = lengthExpr
  if  ((this.marker != jpeg.soi) and (this.marker != jpeg.eoi)) :
    block:
      let on = this.marker
      if on == jpeg.app1:
        let rawDataExpr = this.io.readBytes(int((this.length - 2)))
        this.rawData = rawDataExpr
        let rawDataIo = newKaitaiStream(rawDataExpr)
        let dataExpr = Jpeg_SegmentApp1.read(rawDataIo, this.root, this)
        this.data = dataExpr
      elif on == jpeg.app0:
        let rawDataExpr = this.io.readBytes(int((this.length - 2)))
        this.rawData = rawDataExpr
        let rawDataIo = newKaitaiStream(rawDataExpr)
        let dataExpr = Jpeg_SegmentApp0.read(rawDataIo, this.root, this)
        this.data = dataExpr
      elif on == jpeg.sof0:
        let rawDataExpr = this.io.readBytes(int((this.length - 2)))
        this.rawData = rawDataExpr
        let rawDataIo = newKaitaiStream(rawDataExpr)
        let dataExpr = Jpeg_SegmentSof0.read(rawDataIo, this.root, this)
        this.data = dataExpr
      elif on == jpeg.sos:
        let rawDataExpr = this.io.readBytes(int((this.length - 2)))
        this.rawData = rawDataExpr
        let rawDataIo = newKaitaiStream(rawDataExpr)
        let dataExpr = Jpeg_SegmentSos.read(rawDataIo, this.root, this)
        this.data = dataExpr
      else:
        let dataExpr = this.io.readBytes(int((this.length - 2)))
        this.data = dataExpr
  if this.marker == jpeg.sos:
    let imageDataExpr = this.io.readBytesFull()
    this.imageData = imageDataExpr

proc fromFile*(_: typedesc[Jpeg_Segment], filename: string): Jpeg_Segment =
  Jpeg_Segment.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_SegmentSos], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentSos =
  template this: untyped = result
  this = new(Jpeg_SegmentSos)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Number of components in scan
  ]##
  let numComponentsExpr = this.io.readU1()
  this.numComponents = numComponentsExpr

  ##[
  Scan components specification
  ]##
  for i in 0 ..< int(this.numComponents):
    let it = Jpeg_SegmentSos_Component.read(this.io, this.root, this)
    this.components.add(it)

  ##[
  Start of spectral selection or predictor selection
  ]##
  let startSpectralSelectionExpr = this.io.readU1()
  this.startSpectralSelection = startSpectralSelectionExpr

  ##[
  End of spectral selection
  ]##
  let endSpectralExpr = this.io.readU1()
  this.endSpectral = endSpectralExpr

  ##[
  Successive approximation bit position high + Successive approximation bit position low or point transform
  ]##
  let apprBitPosExpr = this.io.readU1()
  this.apprBitPos = apprBitPosExpr

proc fromFile*(_: typedesc[Jpeg_SegmentSos], filename: string): Jpeg_SegmentSos =
  Jpeg_SegmentSos.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_SegmentSos_Component], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_SegmentSos): Jpeg_SegmentSos_Component =
  template this: untyped = result
  this = new(Jpeg_SegmentSos_Component)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Scan component selector
  ]##
  let idExpr = Jpeg_ComponentId(this.io.readU1())
  this.id = idExpr
  let huffmanTableExpr = this.io.readU1()
  this.huffmanTable = huffmanTableExpr

proc fromFile*(_: typedesc[Jpeg_SegmentSos_Component], filename: string): Jpeg_SegmentSos_Component =
  Jpeg_SegmentSos_Component.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_SegmentApp1], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentApp1 =
  template this: untyped = result
  this = new(Jpeg_SegmentApp1)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = encode(this.io.readBytesTerm(0, false, true, true), "ASCII")
  this.magic = magicExpr
  block:
    let on = this.magic
    if on == "Exif":
      let bodyExpr = Jpeg_ExifInJpeg.read(this.io, this.root, this)
      this.body = bodyExpr

proc fromFile*(_: typedesc[Jpeg_SegmentApp1], filename: string): Jpeg_SegmentApp1 =
  Jpeg_SegmentApp1.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_SegmentSof0], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentSof0 =
  template this: untyped = result
  this = new(Jpeg_SegmentSof0)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent

  let bitsPerSampleExpr = this.io.readU1()
  this.bitsPerSample = bitsPerSampleExpr
  let imageHeightExpr = this.io.readU2be()
  this.imageHeight = imageHeightExpr
  let imageWidthExpr = this.io.readU2be()
  this.imageWidth = imageWidthExpr
  let numComponentsExpr = this.io.readU1()
  this.numComponents = numComponentsExpr
  for i in 0 ..< int(this.numComponents):
    let it = Jpeg_SegmentSof0_Component.read(this.io, this.root, this)
    this.components.add(it)

proc fromFile*(_: typedesc[Jpeg_SegmentSof0], filename: string): Jpeg_SegmentSof0 =
  Jpeg_SegmentSof0.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_SegmentSof0_Component], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_SegmentSof0): Jpeg_SegmentSof0_Component =
  template this: untyped = result
  this = new(Jpeg_SegmentSof0_Component)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  Component selector
  ]##
  let idExpr = Jpeg_ComponentId(this.io.readU1())
  this.id = idExpr
  let samplingFactorsExpr = this.io.readU1()
  this.samplingFactors = samplingFactorsExpr
  let quantizationTableIdExpr = this.io.readU1()
  this.quantizationTableId = quantizationTableIdExpr

proc samplingX(this: Jpeg_SegmentSof0_Component): int = 
  if this.samplingXInstFlag:
    return this.samplingXInst
  let samplingXInstExpr = int(((this.samplingFactors and 240) shr 4))
  this.samplingXInst = samplingXInstExpr
  this.samplingXInstFlag = true
  return this.samplingXInst

proc samplingY(this: Jpeg_SegmentSof0_Component): int = 
  if this.samplingYInstFlag:
    return this.samplingYInst
  let samplingYInstExpr = int((this.samplingFactors and 15))
  this.samplingYInst = samplingYInstExpr
  this.samplingYInstFlag = true
  return this.samplingYInst

proc fromFile*(_: typedesc[Jpeg_SegmentSof0_Component], filename: string): Jpeg_SegmentSof0_Component =
  Jpeg_SegmentSof0_Component.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_ExifInJpeg], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_SegmentApp1): Jpeg_ExifInJpeg =
  template this: untyped = result
  this = new(Jpeg_ExifInJpeg)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent

  let extraZeroExpr = this.io.readBytes(int(1))
  this.extraZero = extraZeroExpr
  let rawDataExpr = this.io.readBytesFull()
  this.rawData = rawDataExpr
  let rawDataIo = newKaitaiStream(rawDataExpr)
  let dataExpr = Exif.read(rawDataIo, this.root, this)
  this.data = dataExpr

proc fromFile*(_: typedesc[Jpeg_ExifInJpeg], filename: string): Jpeg_ExifInJpeg =
  Jpeg_ExifInJpeg.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Jpeg_SegmentApp0], io: KaitaiStream, root: KaitaiStruct, parent: Jpeg_Segment): Jpeg_SegmentApp0 =
  template this: untyped = result
  this = new(Jpeg_SegmentApp0)
  let root = if root == nil: cast[Jpeg](this) else: cast[Jpeg](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = encode(this.io.readBytes(int(5)), "ASCII")
  this.magic = magicExpr
  let versionMajorExpr = this.io.readU1()
  this.versionMajor = versionMajorExpr
  let versionMinorExpr = this.io.readU1()
  this.versionMinor = versionMinorExpr
  let densityUnitsExpr = Jpeg_SegmentApp0_DensityUnit(this.io.readU1())
  this.densityUnits = densityUnitsExpr

  ##[
  Horizontal pixel density. Must not be zero.
  ]##
  let densityXExpr = this.io.readU2be()
  this.densityX = densityXExpr

  ##[
  Vertical pixel density. Must not be zero.
  ]##
  let densityYExpr = this.io.readU2be()
  this.densityY = densityYExpr

  ##[
  Horizontal pixel count of the following embedded RGB thumbnail. May be zero.
  ]##
  let thumbnailXExpr = this.io.readU1()
  this.thumbnailX = thumbnailXExpr

  ##[
  Vertical pixel count of the following embedded RGB thumbnail. May be zero.
  ]##
  let thumbnailYExpr = this.io.readU1()
  this.thumbnailY = thumbnailYExpr

  ##[
  Uncompressed 24 bit RGB (8 bits per color channel) raster thumbnail data in the order R0, G0, B0, ... Rn, Gn, Bn
  ]##
  let thumbnailExpr = this.io.readBytes(int(((this.thumbnailX * this.thumbnailY) * 3)))
  this.thumbnail = thumbnailExpr

proc fromFile*(_: typedesc[Jpeg_SegmentApp0], filename: string): Jpeg_SegmentApp0 =
  Jpeg_SegmentApp0.read(newKaitaiFileStream(filename), nil, nil)