Class: IiifPrint::JP2ImageMetadata

Inherits:
Object
  • Object
show all
Defined in:
lib/iiif_print/jp2_image_metadata.rb

Constant Summary collapse

TOKEN_MARKER_START =
"\xFF".force_encoding("BINARY").freeze
TOKEN_MARKER_SIZ =
"\x51".force_encoding("BINARY").freeze
TOKEN_IHDR =
'ihdr'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ JP2ImageMetadata

Returns a new instance of JP2ImageMetadata.



9
10
11
# File 'lib/iiif_print/jp2_image_metadata.rb', line 9

def initialize(path)
  @path = path
end

Instance Attribute Details

#pathObject

Returns the value of attribute path.



7
8
9
# File 'lib/iiif_print/jp2_image_metadata.rb', line 7

def path
  @path
end

Instance Method Details

#extract_jp2_components(io) ⇒ Array(Integer, Integer)

Returns number components, bits-per-component.

Parameters:

  • io (IO)

    IO stream opened in binary mode, for reading

Returns:

  • (Array(Integer, Integer))

    number components, bits-per-component

Raises:

  • (IOError)


43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/iiif_print/jp2_image_metadata.rb', line 43

def extract_jp2_components(io)
  raise IOError, 'file not open in binary mode' unless io.binmode?
  io.seek(0, IO::SEEK_SET)
  # IHDR should be in first 64 bytes
  buffer = io.read(64)
  ihdr_data = buffer.split(TOKEN_IHDR)[-1]
  raise IOError if ihdr_data.nil?
  num_components = ihdr_data.byteslice(8, 2).unpack('n').first
  # stored as "bit depth of the components in the codestream, minus 1", so add 1
  bits_per_component = ihdr_data.byteslice(10, 1).unpack('c').first + 1
  [num_components, bits_per_component]
end

#extract_jp2_dim(io) ⇒ Array(Integer, Integer)

Returns X size, Y size, in Integer-typed px.

Parameters:

  • io (IO)

    IO stream opened in binary mode, for reading

Returns:

  • (Array(Integer, Integer))

    X size, Y size, in Integer-typed px

Raises:

  • (IOError)


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/iiif_print/jp2_image_metadata.rb', line 15

def extract_jp2_dim(io)
  raise IOError, 'file not open in binary mode' unless io.binmode?
  buffer = ''
  siz_found = false
  # Informed by ISO/IEC 15444-1:2000, pp. 26-27
  #   via:
  #   http://hosting.astro.cornell.edu/~carcich/LRO/jp2/ISO_JPEG200_Standard/INCITS+ISO+IEC+15444-1-2000.pdf
  #
  # first 23 bytes are file-magic, we can skip
  io.seek(23, IO::SEEK_SET)
  while !siz_found && !buffer.nil?
    # read one byte at a time, until we hit marker start 0xFF
    buffer = io.read(1) while buffer != TOKEN_MARKER_START
    # - on 0xFF read subsequent byte; if value != 0x51, continue
    buffer = io.read(1)
    next if buffer != TOKEN_MARKER_SIZ
    # - on 0x51, read next 12 bytes
    buffer = io.read(12)
    siz_found = true
  end
  # discard first 4 bytes; next 4 bytes are XSiz; last 4 bytes are YSiz
  x_siz = buffer.byteslice(4, 4).unpack('N').first
  y_siz = buffer.byteslice(8, 4).unpack('N').first
  [x_siz, y_siz]
end

#technical_metadataHash

Returns hash.

Parameters:

  • path (String)

    path to jp2, for reading

Returns:

  • (Hash)

    hash



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/iiif_print/jp2_image_metadata.rb', line 64

def 
  io = File.open(path, 'rb')
  io.seek(0, IO::SEEK_SET)
  validate_jp2(io)
  x_siz, y_siz = extract_jp2_dim(io)
  nc, bpc = extract_jp2_components(io)
  color = nc >= 3 ? 'color' : 'gray'
  io.close
  {
    color: bpc == 1 ? 'monochrome' : color,
    num_components: nc,
    bits_per_component: bpc,
    width: x_siz,
    height: y_siz
  }
end

#validate_jp2(io) ⇒ Object

Raises:

  • (IOError)


56
57
58
59
60
# File 'lib/iiif_print/jp2_image_metadata.rb', line 56

def validate_jp2(io)
  # verify file is jp2
  magic = io.read(23)
  raise IOError, 'Not JP2 file' unless magic.end_with?('ftypjp2')
end