Module: SafeImage::VipsBackend

Defined in:
lib/safe_image/vips_backend.rb

Constant Summary collapse

DIMENSIONS_RE =
/\A(?:(?<percent>\d+(?:\.\d+)?)%|(?<w>\d*)x(?<h>\d*)(?<only_down>>)?|(?<pixels>\d+)@)\z/

Class Method Summary collapse

Class Method Details

.crop_north(input:, output:, width:, height:, format:, quality: 85, max_pixels: nil) ⇒ Object



9
10
11
# File 'lib/safe_image/vips_backend.rb', line 9

def crop_north(input:, output:, width:, height:, format:, quality: 85, max_pixels: nil)
  Native.crop_north(input.to_s, output.to_s, Integer(width), Integer(height), format.to_s, Integer(quality), max_pixels)
end

.downsize(input:, output:, dimensions:, format:, quality: 85, max_pixels: nil) ⇒ Object



13
14
15
16
17
18
19
20
21
# File 'lib/safe_image/vips_backend.rb', line 13

def downsize(input:, output:, dimensions:, format:, quality: 85, max_pixels: nil)
  probe = SafeImage.probe(input, max_pixels: max_pixels)
  scale = scale_for(probe.width, probe.height, dimensions)
  # Never upscale, but always re-encode through the native saver — even on a
  # no-op scale of 1.0 — so the output is metadata-stripped rather than a
  # verbatim copy of the untrusted input bytes.
  scale = [scale, 1.0].min
  Native.resize(input.to_s, output.to_s, scale, normalized_format(format), Integer(quality), max_pixels)
end

.normalized_format(format) ⇒ Object



23
24
25
26
# File 'lib/safe_image/vips_backend.rb', line 23

def normalized_format(format)
  format = format.to_s.downcase
  format == "jpeg" ? "jpg" : format
end

.scale_for(width, height, dimensions) ⇒ Object

Raises:

  • (ArgumentError)


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/safe_image/vips_backend.rb', line 28

def scale_for(width, height, dimensions)
  dimensions = dimensions.to_s
  match = DIMENSIONS_RE.match(dimensions) or raise ArgumentError, "unsupported dimensions: #{dimensions.inspect}"

  if match[:percent]
    return Float(match[:percent]) / 100.0
  end

  if match[:pixels]
    target_pixels = Float(match[:pixels])
    return Math.sqrt(target_pixels / (Integer(width) * Integer(height)))
  end

  target_w = match[:w].to_s.empty? ? nil : Float(match[:w])
  target_h = match[:h].to_s.empty? ? nil : Float(match[:h])
  scales = []
  scales << target_w / width if target_w
  scales << target_h / height if target_h
  raise ArgumentError, "missing width/height in dimensions: #{dimensions.inspect}" if scales.empty?
  scales.min
end