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: 85, max_pixels: nil) ⇒ Object
- .dominant_color(input, max_pixels: nil) ⇒ Object
- .downsize(input:, output:, dimensions:, format:, quality: 85, max_pixels: nil) ⇒ Object
- .frame_count(input, max_pixels: nil) ⇒ Object
- .letter_avatar(output:, size:, background_rgb:, letter:, pointsize: 280, font: "DejaVu-Sans") ⇒ Object
- .normalized_format(format) ⇒ Object
- .orientation(input, max_pixels: nil) ⇒ Object
- .scale_for(width, height, dimensions) ⇒ Object
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 |
.dominant_color(input, max_pixels: nil) ⇒ Object
23 24 25 26 27 |
# File 'lib/safe_image/vips_backend.rb', line 23 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: 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 |
.frame_count(input, max_pixels: nil) ⇒ Object
29 30 31 32 |
# File 'lib/safe_image/vips_backend.rb', line 29 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
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/safe_image/vips_backend.rb', line 52 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 |
.normalized_format(format) ⇒ Object
80 81 82 83 |
# File 'lib/safe_image/vips_backend.rb', line 80 def normalized_format(format) format = format.to_s.downcase format == "jpeg" ? "jpg" : format end |
.orientation(input, max_pixels: nil) ⇒ Object
34 35 36 37 |
# File 'lib/safe_image/vips_backend.rb', line 34 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
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/safe_image/vips_backend.rb', line 85 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 |