Module: AttachmentSaver::Processors::Image

Included in:
GdkPixbuf, ImageScience, ImageSize, MiniMagick, RMagick
Defined in:
lib/processors/image.rb

Overview

shared code for all image processors

Defined Under Namespace

Modules: Operations

Constant Summary collapse

DEFAULT_VALID_IMAGE_TYPES =
%w(image/jpeg image/png image/gif).freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.from_geometry_string(geom) ⇒ Object

unpacks a resize geometry string into an array contining the corresponding image operation method name (see Operations below, plus any from your chosen image processor) followed by the arguments to that method.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/processors/image.rb', line 116

def self.from_geometry_string(geom)
  match, w, cross, h, flag = geom.match(/^(\d+\.?\d*)?(?:([xX])(\d+\.?\d*)?)?([!%<>#*])?$/).to_a
  raise "couldn't parse geometry string '#{geom}'" if match.nil? || (w.nil? && h.nil?)
  h = w unless cross # there's <w>x<h>, there's <w>x, there's x<h>, and then there's just plain <n>, which means <w>=<h>=<n>
  return [:scale_by, (w || h).to_f/100, (h || w).to_f/100] if flag == '%'
  operation = case flag
    when nil then :scale_to_fit
    when '!' then w && h ? :squish : :scale_to_fit
    when '>' then :shrink_to_fit
    when '<' then :expand_to_fit
    when '*' then :scale_to_cover
    when '#' then :cover_and_crop
  end
  [operation, w ? w.to_i : nil, h ? h.to_i : nil]
end

Instance Method Details

#before_validate_attachmentObject



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/processors/image.rb', line 39

def before_validate_attachment
  unless uploaded_file.nil? || derived_image?
    return false unless valid_image_type
    examine_image
  end
rescue ImageProcessorError
  # we examine all files, regardless of whether the client browser labelled them an
  # image, because they may be an image with the wrong extension or content type.
  # but this will raise for non-image files, so ignore such errors but make sure
  # image? will return false, if it doesn't already.
  self.content_type = "application/octet-stream" if image?
end

#build_derived(attributes) ⇒ Object

builds a new derived image model instance, but doesn’t save it. provided so apps can easily override this step.



100
101
102
# File 'lib/processors/image.rb', line 100

def build_derived(attributes)
  formats.build(attributes)
end

#derived_image?Boolean

determines if this is a derived image. used to prevent infinite recursion when storing the derived images in the same model as the originals (and as a secondary benefit avoid unnecessary work examining images for derived images, for which the full metadata is already filled in by the resizing code).

Returns:

  • (Boolean)


60
61
62
# File 'lib/processors/image.rb', line 60

def derived_image?
  respond_to?(:format_name) && !format_name.blank?
end

#image?Boolean

Returns:

  • (Boolean)


12
13
14
15
16
# File 'lib/processors/image.rb', line 12

def image?
  return false if content_type.blank?
  parts = content_type.split(/\//)
  parts.size == 2 && parts.first.strip == 'image'
end

#process_attachment(filename) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/processors/image.rb', line 72

def process_attachment(filename)
  with_image(filename) do |original_image|
    unless self.class.attachment_options[:formats].blank?
      old_children = new_record? ? {} : formats.group_by(&:format_name)
      self.class.attachment_options[:formats].each do |derived_name, resize_format|
        derived_attributes = process_image(original_image, derived_name, resize_format)
        if derived_attributes
          if old_children[derived_name.to_s]
            update_derived(old_children[derived_name.to_s].pop, derived_attributes)
          else
            build_derived(derived_attributes)
          end
        end
      end
      old_children = old_children.values.flatten
      formats.destroy(old_children) unless old_children.blank? # remove any old derived images for formats for which want_format? now returned false
    end
  end
rescue AttachmentSaverError
  raise
rescue NotImplementedError
  raise
rescue Exception => ex
  raise ImageProcessorError, "#{ex.class}: #{ex.message}", ex.backtrace
end

#process_attachment?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'lib/processors/image.rb', line 52

def process_attachment?
  image? && !self.class.attachment_options[:formats].blank? && !derived_image?
end

#update_derived(derived, attributes) ⇒ Object

updates an existing derived image model instance, and queues it for save when this model is saved. provided so apps can easily override this step.



106
107
108
109
110
111
# File 'lib/processors/image.rb', line 106

def update_derived(derived, attributes)
  derived.attributes = attributes
  @updated_derived_children ||= []
  @updated_derived_children << derived # we don't want to save it just yet in case processing subsequent images fail; rails will automatically save it if we're a new record, but we have to do it ourselves in an after_save if not
  derived
end

#valid_image_typeObject



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/processors/image.rb', line 18

def valid_image_type
  valid_image_types = self.class.attachment_options[:valid_image_types] || DEFAULT_VALID_IMAGE_TYPES

  uploaded_file.rewind
  magic = MimeMagic.by_magic(uploaded_file)

  if magic.nil?
    # if it doesn't look like an image, make sure it's not labelled as an image
    self.content_type = 'application/octet-stream' if image? || content_type.nil?
  elsif !valid_image_types.include?(magic.type)
    # overwrite the content type given by the untrusted client with the real content type; it may get refined later by the image processor
    self.content_type = magic.type
  else
    # seems legit
    return true
  end

  errors.add(:content_type, "is invalid") if respond_to?(:errors)
  false
end

#want_format?(derived_name, derived_width, derived_height) ⇒ Boolean

determines if a particular configured derived image should be created. this implementation, which always returns true, may be overridden by applications to make certain formats conditional (for example, only creating certain larger sizes if the original image was at least that large).

Returns:

  • (Boolean)


68
69
70
# File 'lib/processors/image.rb', line 68

def want_format?(derived_name, derived_width, derived_height)
  true
end