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/- BUNDLED_DEJAVU =
Maps the public font tokens (shared with the ImageMagick backend) to Pango family names. DejaVu Sans additionally pins the font file bundled with the gem, so its rendering does not depend on host fonts.
File.("fonts/DejaVuSans.ttf", __dir__)
- PANGO_FONTS =
{ "DejaVu-Sans" => ["DejaVu Sans", BUNDLED_DEJAVU], "NimbusSans-Regular" => ["Nimbus Sans", nil], "Liberation-Sans" => ["Liberation Sans", nil], "Arial" => ["Arial", nil], "Helvetica" => ["Helvetica", nil], "Adwaita-Sans" => ["Adwaita Sans", nil] }.freeze
Class Method Summary collapse
- .crop_north(input:, output:, width:, height:, format:, quality: QualityDefaults::JPEG, max_pixels: nil) ⇒ Object
- .dominant_color(input, max_pixels: nil) ⇒ Object
- .downsize(input:, output:, dimensions:, format:, quality: QualityDefaults::JPEG, max_pixels: nil) ⇒ Object
- .frame_count(input, max_pixels: nil) ⇒ Object
- .letter_avatar(output:, size:, background_rgb:, letter:, pointsize: 280, font: "DejaVu-Sans") ⇒ Object
- .orientation(input, max_pixels: nil) ⇒ Object
- .scale_for(width, height, dimensions) ⇒ Object
Class Method Details
.crop_north(input:, output:, width:, height:, format:, quality: QualityDefaults::JPEG, max_pixels: nil) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 |
# File 'lib/safe_image/vips_backend.rb', line 9 def crop_north(input:, output:, width:, height:, format:, quality: QualityDefaults::JPEG, max_pixels: nil) Native.crop_north( input.to_s, output.to_s, Integer(width), Integer(height), format.to_s, Integer(quality), max_pixels ) end |
.dominant_color(input, max_pixels: nil) ⇒ Object
31 32 33 34 35 |
# File 'lib/safe_image/vips_backend.rb', line 31 def dominant_color(input, max_pixels: nil) input = PathSafety.ensure_regular_file!(input).to_s rgb = Native.dominant_color(input, max_pixels) format("%02X%02X%02X", *rgb) end |
.downsize(input:, output:, dimensions:, format:, quality: QualityDefaults::JPEG, max_pixels: nil) ⇒ Object
21 22 23 24 25 26 27 28 29 |
# File 'lib/safe_image/vips_backend.rb', line 21 def downsize(input:, output:, dimensions:, format:, quality: QualityDefaults::JPEG, 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, Formats.normalize(format), Integer(quality), max_pixels) end |
.frame_count(input, max_pixels: nil) ⇒ Object
37 38 39 40 |
# File 'lib/safe_image/vips_backend.rb', line 37 def frame_count(input, max_pixels: nil) input = PathSafety.ensure_regular_file!(input).to_s Native.pages(input, max_pixels) end |
.letter_avatar(output:, size:, background_rgb:, letter:, pointsize: 280, font: "DejaVu-Sans") ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/safe_image/vips_backend.rb', line 60 def letter_avatar(output:, size:, background_rgb:, letter:, pointsize: 280, font: "DejaVu-Sans") started = Process.clock_gettime(Process::CLOCK_MONOTONIC) output = PathSafety.ensure_safe_output_path!(output).to_s size = Integer(size) raise ArgumentError, "size must be 1..4096" unless (1..4096).cover?(size) pointsize = Integer(pointsize) raise ArgumentError, "pointsize must be 1..2000" unless (1..2000).cover?(pointsize) rgb = Array(background_rgb).map { |value| Integer(value) } unless rgb.length == 3 && rgb.all? { |value| (0..255).cover?(value) } raise ArgumentError, "background_rgb must have three channels in 0..255" end family, fontfile = PANGO_FONTS.fetch(font.to_s) { raise ArgumentError, "unsupported font: #{font.to_s.inspect}" } fontfile = nil unless fontfile && File.file?(fontfile) # vips_text parses Pango markup, and the glyph derives from user input. glyph = letter.to_s.each_grapheme_cluster.first.to_s.strip markup = glyph.gsub("&", "&").gsub("<", "<").gsub(">", ">") Native.letter_avatar(output, size, rgb[0], rgb[1], rgb[2], markup, "#{family} #{pointsize}", fontfile.to_s) { input_format: "generated", output_format: "png", width: size, height: size, duration_ms: (Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000 } end |
.orientation(input, max_pixels: nil) ⇒ Object
42 43 44 45 |
# File 'lib/safe_image/vips_backend.rb', line 42 def orientation(input, max_pixels: nil) input = PathSafety.ensure_regular_file!(input).to_s Native.orientation(input, max_pixels) end |
.scale_for(width, height, dimensions) ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/safe_image/vips_backend.rb', line 88 def scale_for(width, height, dimensions) dimensions = dimensions.to_s match = DIMENSIONS_RE.match(dimensions) or raise ArgumentError, "unsupported dimensions: #{dimensions.inspect}" return Float(match[:percent]) / 100.0 if match[:percent] 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 |