Class: DynamicImage::Format

Inherits:
Object
  • Object
show all
Defined in:
lib/dynamic_image/format.rb

Overview

DynamicImage Format

A registry of the image formats DynamicImage understands. Each format knows its content types, extensions, the magic bytes that identify it, the options it is saved with, and whether it can hold more than one frame.

Formats are looked up by name, by content type, or by sniffing the first bytes of a file. Uploads are always identified by sniffing, never by the content type the client claims.

Examples:

DynamicImage::Format.find("jpg")           # => the JPEG format
DynamicImage::Format.content_type("image/png")
DynamicImage::Format.sniff(File.binread(path, 32))

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, options) ⇒ Format

Returns a new instance of Format.

Parameters:

  • the format name

  • the format definition, as passed to register

See Also:



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

def initialize(name, options)
  options = default_options.merge(options)

  @name = name
  @animated = options[:animated]
  @alpha = options[:alpha]
  @content_types = Array(options[:content_type])
  @extensions = Array(options[:extension])
  @magic_bytes = options[:magic_bytes].map(&:b)
  @offset = options[:offset]
  @signature = options[:signature]
  @save_options = options[:save_options]
end

Instance Attribute Details

#alphaBoolean (readonly)

Returns whether the format holds an alpha channel.

Returns:

  • whether the format holds an alpha channel



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

#animatedBoolean (readonly)

Returns whether the format holds more than one frame.

Returns:

  • whether the format holds more than one frame



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

#content_typesArray<String> (readonly)

Returns the content types, canonical one first.

Returns:

  • the content types, canonical one first



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

#extensionsArray<String> (readonly)

Returns the file extensions, preferred one first.

Returns:

  • the file extensions, preferred one first



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

#magic_bytesArray<String> (readonly)

Returns byte sequences identifying the format.

Returns:

  • byte sequences identifying the format



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

#nameString (readonly)

Returns the format name, such as "JPEG".

Returns:

  • the format name, such as "JPEG"



36
37
38
# File 'lib/dynamic_image/format.rb', line 36

def name
  @name
end

#offsetInteger (readonly)

Returns where in the header the magic bytes sit.

Returns:

  • where in the header the magic bytes sit



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

#save_optionsHash (readonly)

Returns options passed to vips when writing.

Returns:

  • options passed to vips when writing



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

#signatureObject (readonly)

Returns the value of attribute signature.



36
37
# File 'lib/dynamic_image/format.rb', line 36

attr_reader :name, :animated, :alpha, :content_types, :extensions,
:magic_bytes, :offset, :save_options, :signature

Class Method Details

.content_type(type) ⇒ DynamicImage::Format?

Finds the format for a content type.

Parameters:

  • the content type

Returns:



107
108
109
# File 'lib/dynamic_image/format.rb', line 107

def content_type(type)
  formats.filter { |f| f.content_types.include?(type) }.first
end

.content_typesArray<String>

Every content type of every registered format.

Returns:



114
115
116
# File 'lib/dynamic_image/format.rb', line 114

def content_types
  formats.flat_map(&:content_types)
end

.find(name) ⇒ DynamicImage::Format?

Finds a format by name. Case insensitive, and "JPG" is understood as an alias for "JPEG".

Parameters:

  • the format name

Returns:



122
123
124
125
126
# File 'lib/dynamic_image/format.rb', line 122

def find(name)
  key = name.to_s.upcase
  key = "JPEG" if key == "JPG"
  registered_formats[key]
end

.formatsArray<DynamicImage::Format>

All registered formats.

Returns:



131
132
133
# File 'lib/dynamic_image/format.rb', line 131

def formats
  registered_formats.map { |_, f| f }
end

.iso_brands(bytes) ⇒ Array<String>

The brands declared by an ISO base media file, major brand first, followed by the compatible brands. Empty for anything that isn't an ftyp box.

Parameters:

  • the file header

Returns:



162
163
164
165
166
# File 'lib/dynamic_image/format.rb', line 162

def iso_brands(bytes)
  return [] unless bytes.to_s.bytesize >= 12 && bytes[4, 4] == "ftyp".b

  [bytes[8, 4]] + bytes[16...bytes.unpack1("N")].to_s.scan(/.{4}/m)
end

.register(name, **opts) ⇒ DynamicImage::Format

Registers a format.

Each option sets the attribute of the same name, except content_type and extension, which are singular here and accept either one value or a list. Anything left out falls back to #default_options.

Parameters:

  • the format name, uppercase by convention

  • the format definition

Returns:

  • the registered format



143
144
145
# File 'lib/dynamic_image/format.rb', line 143

def register(name, **opts)
  registered_formats[name] = new(name, opts)
end

.sniff(bytes) ⇒ DynamicImage::Format?

Identifies a format from the first bytes of a file.

Parameters:

  • the file header

Returns:

  • the format, if recognized



151
152
153
154
155
# File 'lib/dynamic_image/format.rb', line 151

def sniff(bytes)
  return unless bytes

  formats.find { |format| format.matches?(bytes) }
end

Instance Method Details

#alpha?Boolean

Returns true if the format supports an alpha channel.

Returns:



66
67
68
# File 'lib/dynamic_image/format.rb', line 66

def alpha?
  alpha
end

#animated?Boolean

Returns true if the format supports multiple frames.

Returns:



59
60
61
# File 'lib/dynamic_image/format.rb', line 59

def animated?
  animated
end

#content_typeString

The canonical content type.

Returns:



84
85
86
# File 'lib/dynamic_image/format.rb', line 84

def content_type
  content_types.first
end

#default_optionsHash

Defaults every format definition is merged over.

Returns:



178
179
180
181
# File 'lib/dynamic_image/format.rb', line 178

def default_options
  { animated: false, alpha: false, content_type: [], extension: [],
    magic_bytes: [], offset: 0, signature: nil, save_options: {} }
end

#extensionString

The preferred file extension, leading dot included.

Returns:



91
92
93
# File 'lib/dynamic_image/format.rb', line 91

def extension
  extensions.first
end

#matches?(bytes) ⇒ Boolean

Returns true if the given header belongs to this format.

Parameters:

  • the first bytes of the file

Returns:



74
75
76
77
78
79
# File 'lib/dynamic_image/format.rb', line 74

def matches?(bytes)
  header = bytes.to_s[offset..].to_s
  return false unless magic_bytes.any? { |b| header.start_with?(b) }

  signature.nil? || signature.call(bytes)
end

#mime_typeMime::Type

The canonical content type as a Mime::Type.

Returns:



98
99
100
# File 'lib/dynamic_image/format.rb', line 98

def mime_type
  Mime::Type.lookup(content_type)
end