imgproxy 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +235 -112
  3. data/lib/imgproxy.rb +70 -50
  4. data/lib/imgproxy/builder.rb +47 -55
  5. data/lib/imgproxy/config.rb +96 -30
  6. data/lib/imgproxy/extensions/active_storage.rb +10 -0
  7. data/lib/imgproxy/extensions/shrine.rb +10 -0
  8. data/lib/imgproxy/options.rb +83 -121
  9. data/lib/imgproxy/options_aliases.rb +43 -0
  10. data/lib/imgproxy/options_casters/adjust.rb +22 -0
  11. data/lib/imgproxy/options_casters/array.rb +12 -0
  12. data/lib/imgproxy/options_casters/base64.rb +12 -0
  13. data/lib/imgproxy/options_casters/bool.rb +12 -0
  14. data/lib/imgproxy/options_casters/crop.rb +23 -0
  15. data/lib/imgproxy/options_casters/extend.rb +26 -0
  16. data/lib/imgproxy/options_casters/float.rb +16 -0
  17. data/lib/imgproxy/options_casters/gif_options.rb +21 -0
  18. data/lib/imgproxy/options_casters/gravity.rb +23 -0
  19. data/lib/imgproxy/options_casters/group.rb +21 -0
  20. data/lib/imgproxy/options_casters/integer.rb +10 -0
  21. data/lib/imgproxy/options_casters/jpeg_options.rb +26 -0
  22. data/lib/imgproxy/options_casters/png_options.rb +23 -0
  23. data/lib/imgproxy/options_casters/resize.rb +21 -0
  24. data/lib/imgproxy/options_casters/size.rb +24 -0
  25. data/lib/imgproxy/options_casters/string.rb +10 -0
  26. data/lib/imgproxy/options_casters/trim.rb +28 -0
  27. data/lib/imgproxy/options_casters/watermark.rb +30 -0
  28. data/lib/imgproxy/trim_array.rb +11 -0
  29. data/lib/imgproxy/url_adapters.rb +0 -4
  30. data/lib/imgproxy/url_adapters/active_storage.rb +25 -0
  31. data/lib/imgproxy/url_adapters/shrine.rb +15 -5
  32. data/lib/imgproxy/version.rb +1 -1
  33. metadata +54 -9
  34. data/lib/imgproxy/url_adapters/active_storage_gcs.rb +0 -31
  35. data/lib/imgproxy/url_adapters/active_storage_s3.rb +0 -23
  36. data/lib/imgproxy/url_adapters/shrine_s3.rb +0 -21
@@ -3,6 +3,7 @@ require "base64"
3
3
  require "erb"
4
4
 
5
5
  require "imgproxy/options"
6
+ require "imgproxy/options_aliases"
6
7
 
7
8
  module Imgproxy
8
9
  # Builds imgproxy URL
@@ -17,19 +18,15 @@ module Imgproxy
17
18
  # builder.url_for("http://images.example.com/images/image1.jpg")
18
19
  # builder.url_for("http://images.example.com/images/image2.jpg")
19
20
  class Builder
20
- OMITTED_OPTIONS = %i[format].freeze
21
21
  # @param [Hash] options Processing options
22
22
  # @see Imgproxy.url_for
23
23
  def initialize(options = {})
24
24
  options = options.dup
25
25
 
26
- @base64_encode_url = options.delete(:base64_encode_url)
27
- @use_short_options = options.delete(:use_short_options)
28
-
29
- @use_short_options = config.use_short_options if @use_short_options.nil?
30
- @base64_encode_url = config.base64_encode_urls if @base64_encode_url.nil?
26
+ extract_builder_options(options)
31
27
 
32
28
  @options = Imgproxy::Options.new(options)
29
+ @format = @options.delete(:format)
33
30
  end
34
31
 
35
32
  # Genrates imgproxy URL
@@ -39,77 +36,68 @@ module Imgproxy
39
36
  # the configured URL adapters
40
37
  # @see Imgproxy.url_for
41
38
  def url_for(image)
42
- path = [*processing_options, url(image)].join("/")
39
+ path = [*processing_options, url(image, ext: @format)].join("/")
43
40
  signature = sign_path(path)
44
41
 
45
42
  File.join(Imgproxy.config.endpoint.to_s, signature, path)
46
43
  end
47
44
 
48
- private
45
+ # Genrates imgproxy info URL
46
+ #
47
+ # @return [String] imgproxy info URL
48
+ # @param [String,URI, Object] image Source image URL or object applicable for
49
+ # the configured URL adapters
50
+ # @see Imgproxy.info_url_for
51
+ def info_url_for(image)
52
+ path = url(image)
53
+ signature = sign_path(path)
49
54
 
50
- OPTIONS_ALIASES = {
51
- resize: :rs,
52
- size: :s,
53
- resizing_type: :rt,
54
- width: :w,
55
- height: :h,
56
- enlarge: :en,
57
- extend: :ex,
58
- gravity: :g,
59
- crop: :c,
60
- padding: :pd,
61
- trim: :t,
62
- quality: :q,
63
- max_bytes: :mb,
64
- background: :bg,
65
- adjust: :a,
66
- brightness: :br,
67
- contrast: :co,
68
- saturation: :sa,
69
- blur: :bl,
70
- sharpen: :sh,
71
- pixelate: :pix,
72
- watermark: :wm,
73
- watermark_url: :wmu,
74
- preset: :pr,
75
- cachebuster: :cb,
76
- }.freeze
55
+ File.join(Imgproxy.config.endpoint.to_s, "info", signature, path)
56
+ end
57
+
58
+ private
77
59
 
78
60
  NEED_ESCAPE_RE = /[@?% ]|[^\p{Ascii}]/.freeze
79
61
 
62
+ def extract_builder_options(options)
63
+ @use_short_options = not_nil_or(options.delete(:use_short_options), config.use_short_options)
64
+ @base64_encode_url = not_nil_or(options.delete(:base64_encode_url), config.base64_encode_urls)
65
+ @escape_plain_url =
66
+ not_nil_or(options.delete(:escape_plain_url), config.always_escape_plain_urls)
67
+ end
68
+
80
69
  def processing_options
81
- @processing_options ||=
82
- @options.reject { |k, _| OMITTED_OPTIONS.include?(k) }.map do |key, value|
83
- "#{option_alias(key)}:#{wrap_array(value).join(':')}"
84
- end
70
+ @processing_options ||= @options.map do |key, value|
71
+ [option_alias(key), value].join(":")
72
+ end
85
73
  end
86
74
 
87
- def plain_url_for(url)
88
- escaped_url = url.match?(NEED_ESCAPE_RE) ? ERB::Util.url_encode(url) : url
75
+ def url(image, ext: nil)
76
+ url = config.url_adapters.url_of(image)
89
77
 
90
- @options[:format] ? "plain/#{escaped_url}@#{@options[:format]}" : "plain/#{escaped_url}"
78
+ @base64_encode_url ? base64_url_for(url, ext: ext) : plain_url_for(url, ext: ext)
91
79
  end
92
80
 
93
- def base64_url_for(url)
94
- encoded_url = Base64.urlsafe_encode64(url).tr("=", "").scan(/.{1,16}/).join("/")
81
+ def plain_url_for(url, ext: nil)
82
+ escaped_url = need_escape_url?(url) ? ERB::Util.url_encode(url) : url
95
83
 
96
- @options[:format] ? "#{encoded_url}.#{@options[:format]}" : encoded_url
84
+ ext ? "plain/#{escaped_url}@#{ext}" : "plain/#{escaped_url}"
97
85
  end
98
86
 
99
- def option_alias(name)
100
- return name unless config.use_short_options
87
+ def base64_url_for(url, ext: nil)
88
+ encoded_url = Base64.urlsafe_encode64(url).tr("=", "").scan(/.{1,16}/).join("/")
101
89
 
102
- OPTIONS_ALIASES.fetch(name, name)
90
+ ext ? "#{encoded_url}.#{ext}" : encoded_url
103
91
  end
104
92
 
105
- def wrap_array(value)
106
- value.is_a?(Array) ? value : [value]
93
+ def need_escape_url?(url)
94
+ @escape_plain_url || url.match?(NEED_ESCAPE_RE)
107
95
  end
108
96
 
109
- def url(image)
110
- url = config.url_adapters.url_of(image)
97
+ def option_alias(name)
98
+ return name unless @use_short_options
111
99
 
112
- @base64_encode_url ? base64_url_for(url) : plain_url_for(url)
100
+ Imgproxy::OPTIONS_ALIASES.fetch(name, name)
113
101
  end
114
102
 
115
103
  def sign_path(path)
@@ -130,11 +118,11 @@ module Imgproxy
130
118
  end
131
119
 
132
120
  def signature_key
133
- config.key
121
+ config.raw_key
134
122
  end
135
123
 
136
124
  def signature_salt
137
- config.salt
125
+ config.raw_salt
138
126
  end
139
127
 
140
128
  def signature_size
@@ -144,5 +132,9 @@ module Imgproxy
144
132
  def config
145
133
  Imgproxy.config
146
134
  end
135
+
136
+ def not_nil_or(value, fallback)
137
+ value.nil? ? fallback : value
138
+ end
147
139
  end
148
140
  end
@@ -1,44 +1,110 @@
1
+ require "anyway_config"
2
+
1
3
  require "imgproxy/url_adapters"
2
4
 
3
5
  module Imgproxy
4
6
  # Imgproxy config
7
+ #
8
+ # @!attribute endpoint
9
+ # imgproxy endpoint
10
+ # @return [String]
11
+ # @!attribute key
12
+ # imgproxy hex-encoded signature key
13
+ # @return [String]
14
+ # @!attribute salt
15
+ # imgproxy hex-encoded signature salt
16
+ # @return [String]
17
+ # @!attribute raw_key
18
+ # Decoded signature key
19
+ # @return [String]
20
+ # @!attribute raw_salt
21
+ # Decoded signature salt
22
+ # @return [String]
23
+ # @!attribute signature_size
24
+ # imgproxy signature size. Defaults to 32
25
+ # @return [String]
26
+ # @!attribute use_short_options
27
+ # Use short processing option names (+rs+ for +resize+, +g+ for +gravity+, etc).
28
+ # Defaults to true
29
+ # @return [String]
30
+ # @!attribute base64_encode_urls
31
+ # Base64 encode the URL. Defaults to false
32
+ # @return [String]
33
+ # @!attribute always_escape_plain_urls
34
+ # Always escape plain URLs. Defaults to false
35
+ # @return [String]
36
+ # @!attribute use_s3_urls
37
+ # Use short S3 urls (s3://...) when possible. Defaults to false
38
+ # @return [String]
39
+ # @!attribute use_gcs_urls
40
+ # Use short Google Cloud Storage urls (gs://...) when possible. Defaults to false
41
+ # @return [String]
42
+ # @!attribute gcs_bucket
43
+ # Google Cloud Storage bucket name
44
+ # @return [String]
45
+ # @!attribute shrine_host
46
+ # Shrine host
47
+ # @return [String]
48
+ #
5
49
  # @see Imgproxy.configure
6
- class Config
7
- # @return [String] imgproxy endpoint
8
- attr_accessor :endpoint
9
- # @return [String] imgproxy signature key
10
- attr_accessor :key
11
- # @return [String] imgproxy signature salt
12
- attr_accessor :salt
13
- # @return [Integer] imgproxy signature size. Defaults to 32
14
- attr_accessor :signature_size
15
- # @return [Boolean] use short processing option names
16
- # (`rs` for `resize`, `g` for `gravity`, etc).
17
- # Defaults to true
18
- attr_accessor :use_short_options
19
-
20
- # @return [Boolean] base64 encode the URL
21
- # Defaults to false
22
- attr_accessor :base64_encode_urls
23
-
24
- def initialize
25
- self.signature_size = 32
26
- self.use_short_options = true
27
- self.base64_encode_urls = false
50
+ # @see https://github.com/palkan/anyway_config anyway_config
51
+ class Config < Anyway::Config
52
+ attr_config(
53
+ :endpoint,
54
+ :key,
55
+ :salt,
56
+ :raw_key,
57
+ :raw_salt,
58
+ signature_size: 32,
59
+ use_short_options: true,
60
+ base64_encode_urls: false,
61
+ always_escape_plain_urls: false,
62
+ use_s3_urls: false,
63
+ use_gcs_urls: false,
64
+ gcs_bucket: nil,
65
+ shrine_host: nil,
66
+ )
67
+
68
+ alias_method :set_key, :key=
69
+ alias_method :set_raw_key, :raw_key=
70
+ alias_method :set_salt, :salt=
71
+ alias_method :set_raw_salt, :raw_salt=
72
+ private :set_key, :set_raw_key, :set_salt, :set_raw_salt
73
+
74
+ def key=(value)
75
+ value = value&.to_s
76
+ super(value)
77
+ set_raw_key(value && [value].pack("H*"))
28
78
  end
29
79
 
30
- # Decodes hex-encoded key and sets it to {#key}
31
- #
32
- # @param value [String] hex-encoded signature key
80
+ def raw_key=(value)
81
+ value = value&.to_s
82
+ super(value)
83
+ set_key(value&.unpack("H*")&.first)
84
+ end
85
+
86
+ def salt=(value)
87
+ value = value&.to_s
88
+ super(value)
89
+ set_raw_salt(value && [value].pack("H*"))
90
+ end
91
+
92
+ def raw_salt=(value)
93
+ value = value&.to_s
94
+ super(value)
95
+ set_salt(value&.unpack("H*")&.first)
96
+ end
97
+
98
+ # @deprecated Please use {#key} instead
33
99
  def hex_key=(value)
34
- self.key = value.nil? ? nil : [value].pack("H*")
100
+ warn "[DEPRECATION] #hex_key is deprecated. Please use #key instead."
101
+ self.key = value
35
102
  end
36
103
 
37
- # Decodes hex-encoded salt and sets it to {#salt}
38
- #
39
- # @param value [String] hex-encoded signature salt
104
+ # @deprecated Please use {#salt} instead
40
105
  def hex_salt=(value)
41
- self.salt = value.nil? ? nil : [value].pack("H*")
106
+ warn "[DEPRECATION] #hex_salt is deprecated. Please use #salt instead."
107
+ self.salt = value
42
108
  end
43
109
 
44
110
  # URL adapters config. Allows to use this gem with ActiveStorage, Shrine, etc.
@@ -12,6 +12,16 @@ module Imgproxy
12
12
  return options.url_for(self) if options.is_a?(Imgproxy::Builder)
13
13
  Imgproxy.url_for(self, options)
14
14
  end
15
+
16
+ # Returns imgproxy info URL for an attachment
17
+ #
18
+ # @return [String]
19
+ # @param options [Hash, Imgproxy::Builder]
20
+ # @see Imgproxy.info_url_for
21
+ def imgproxy_info_url(options = {})
22
+ return options.info_url_for(self) if options.is_a?(Imgproxy::Builder)
23
+ Imgproxy.info_url_for(self, options)
24
+ end
15
25
  end
16
26
  end
17
27
  end
@@ -12,6 +12,16 @@ module Imgproxy
12
12
  return options.url_for(self) if options.is_a?(Imgproxy::Builder)
13
13
  Imgproxy.url_for(self, options)
14
14
  end
15
+
16
+ # Returns imgproxy info URL for a Shrine::UploadedFile instance
17
+ #
18
+ # @return [String]
19
+ # @param options [Hash, Imgproxy::Builder]
20
+ # @see Imgproxy.info_url_for
21
+ def imgproxy_info_url(options = {})
22
+ return options.info_url_for(self) if options.is_a?(Imgproxy::Builder)
23
+ Imgproxy.info_url_for(self, options)
24
+ end
15
25
  end
16
26
  end
17
27
  end
@@ -1,153 +1,115 @@
1
+ require "imgproxy/trim_array"
2
+ require "imgproxy/options_casters/string"
3
+ require "imgproxy/options_casters/integer"
4
+ require "imgproxy/options_casters/float"
5
+ require "imgproxy/options_casters/bool"
6
+ require "imgproxy/options_casters/array"
7
+ require "imgproxy/options_casters/base64"
8
+ require "imgproxy/options_casters/resize"
9
+ require "imgproxy/options_casters/size"
10
+ require "imgproxy/options_casters/extend"
11
+ require "imgproxy/options_casters/gravity"
12
+ require "imgproxy/options_casters/crop"
13
+ require "imgproxy/options_casters/trim"
14
+ require "imgproxy/options_casters/adjust"
15
+ require "imgproxy/options_casters/watermark"
16
+ require "imgproxy/options_casters/jpeg_options"
17
+ require "imgproxy/options_casters/png_options"
18
+ require "imgproxy/options_casters/gif_options"
19
+
1
20
  module Imgproxy
2
21
  # Formats and regroups processing options
3
22
  class Options < Hash
4
- STRING_OPTS = %i[resizing_type extend_gravity gravity crop_gravity trim_color watermark_position
5
- watermark_url style cachebuster format base64_encode_url].freeze
6
- INT_OPTS = %i[width height crop_width crop_height trim_threshold quality brightness pixelate
7
- watermark_x_offset watermark_y_offset max_bytes].freeze
8
- FLOAT_OPTS = %i[dpr extend_gravity_x extend_gravity_y gravity_x gravity_y crop_gravity_x
9
- crop_gravity_y contrast saturation blur sharpen watermark_opacity
10
- watermark_scale].freeze
11
- BOOL_OPTS = %i[enlarge extend trim_equal_hor trim_equal_ver].freeze
12
- ARRAY_OPTS = %i[padding background preset].freeze
13
- ALL_OPTS = (STRING_OPTS + INT_OPTS + FLOAT_OPTS + BOOL_OPTS + ARRAY_OPTS).freeze
14
-
15
- OPTS_PRIORITY = %i[ resize size resizing_type width height dpr enlarge extend gravity
16
- crop padding trim quality max_bytes background adjust brightness contrast
17
- saturation blur sharpen pixelate watermark watermark_url style preset
18
- cachebuster ].freeze
23
+ using TrimArray
24
+
25
+ CASTERS = {
26
+ resize: Imgproxy::OptionsCasters::Resize,
27
+ size: Imgproxy::OptionsCasters::Size,
28
+ resizing_type: Imgproxy::OptionsCasters::String,
29
+ resizing_algorithm: Imgproxy::OptionsCasters::String,
30
+ width: Imgproxy::OptionsCasters::Integer,
31
+ height: Imgproxy::OptionsCasters::Integer,
32
+ dpr: Imgproxy::OptionsCasters::Float,
33
+ enlarge: Imgproxy::OptionsCasters::Bool,
34
+ extend: Imgproxy::OptionsCasters::Extend,
35
+ gravity: Imgproxy::OptionsCasters::Gravity,
36
+ crop: Imgproxy::OptionsCasters::Crop,
37
+ padding: Imgproxy::OptionsCasters::Array,
38
+ trim: Imgproxy::OptionsCasters::Trim,
39
+ rotate: Imgproxy::OptionsCasters::Integer,
40
+ quality: Imgproxy::OptionsCasters::Integer,
41
+ max_bytes: Imgproxy::OptionsCasters::Integer,
42
+ background: Imgproxy::OptionsCasters::Array,
43
+ background_alpha: Imgproxy::OptionsCasters::Float,
44
+ adjust: Imgproxy::OptionsCasters::Adjust,
45
+ brightness: Imgproxy::OptionsCasters::Integer,
46
+ contrast: Imgproxy::OptionsCasters::Float,
47
+ saturation: Imgproxy::OptionsCasters::Float,
48
+ blur: Imgproxy::OptionsCasters::Float,
49
+ sharpen: Imgproxy::OptionsCasters::Float,
50
+ pixelate: Imgproxy::OptionsCasters::Integer,
51
+ unsharpening: Imgproxy::OptionsCasters::String,
52
+ watermark: Imgproxy::OptionsCasters::Watermark,
53
+ watermark_url: Imgproxy::OptionsCasters::Base64,
54
+ style: Imgproxy::OptionsCasters::Base64,
55
+ jpeg_options: Imgproxy::OptionsCasters::JpegOptions,
56
+ png_options: Imgproxy::OptionsCasters::PngOptions,
57
+ gif_options: Imgproxy::OptionsCasters::GifOptions,
58
+ page: Imgproxy::OptionsCasters::Integer,
59
+ video_thumbnail_second: Imgproxy::OptionsCasters::Integer,
60
+ preset: Imgproxy::OptionsCasters::Array,
61
+ cachebuster: Imgproxy::OptionsCasters::String,
62
+ strip_metadata: Imgproxy::OptionsCasters::Bool,
63
+ strip_color_profile: Imgproxy::OptionsCasters::Bool,
64
+ auto_rotate: Imgproxy::OptionsCasters::Bool,
65
+ filename: Imgproxy::OptionsCasters::String,
66
+ format: Imgproxy::OptionsCasters::String,
67
+ }.freeze
68
+
69
+ META = %i[size resize adjust].freeze
19
70
 
20
71
  # @param options [Hash] raw processing options
21
72
  def initialize(options)
22
- merge!(options.slice(*ALL_OPTS))
23
-
24
- typecast
25
-
26
- group_options
27
-
28
- encode_style
29
- encode_watermark_url
30
-
31
- replace(Hash[sort_by { |k, _| OPTS_PRIORITY.index(k) || 99 }])
32
-
33
- freeze
34
- end
35
-
36
- private
73
+ # Options order hack: initialize known and meta options with nil value to preserve order
74
+ CASTERS.each_key { |n| self[n] = nil if options.key?(n) || META.include?(n) }
37
75
 
38
- def typecast
39
- compact.each do |key, value|
40
- self[key] =
41
- case key
42
- when *STRING_OPTS then value.to_s
43
- when *INT_OPTS then value.to_i
44
- when *FLOAT_OPTS then value.to_f
45
- when *BOOL_OPTS then bool(value)
46
- when *ARRAY_OPTS then wrap_array(value)
47
- end
76
+ options.each do |name, value|
77
+ caster = CASTERS[name]
78
+ self[name] = caster ? caster.cast(value) : unwrap_hash(value)
48
79
  end
49
- end
50
-
51
- def bool(value)
52
- value && value != 0 && value != "0" ? 1 : 0
53
- end
54
-
55
- def wrap_array(value)
56
- value.is_a?(Array) ? value : [value]
57
- end
58
80
 
59
- def group_options
60
- group_crop_opts
61
- group_extend_opts
62
81
  group_resizing_opts
63
- group_gravity_opts
64
82
  group_adjust_opts
65
- group_watermark_opts
66
- group_trim_opts
67
- end
68
83
 
69
- def group_crop_opts
70
- crop_width = delete(:crop_width)
71
- crop_height = delete(:crop_height)
72
- crop_gravity = extract_and_trim_nils(:crop_gravity, :crop_gravity_x, :crop_gravity_y)
84
+ compact!
85
+ end
73
86
 
74
- return unless crop_width || crop_height
87
+ private
75
88
 
76
- crop_gravity = nil if crop_gravity[0].nil?
89
+ def unwrap_hash(raw)
90
+ return raw unless raw.is_a?(Hash)
77
91
 
78
- self[:crop] = [crop_width || 0, crop_height || 0, *crop_gravity]
92
+ raw.flat_map do |_key, val|
93
+ unwrap_hash(val)
94
+ end
79
95
  end
80
96
 
81
97
  def group_resizing_opts
82
- return unless self[:width] && self[:height]
98
+ return unless self[:width] && self[:height] && !self[:size] && !self[:resize]
83
99
 
84
100
  self[:size] = extract_and_trim_nils(:width, :height, :enlarge, :extend)
85
-
86
101
  self[:resize] = [delete(:resizing_type), *delete(:size)] if self[:resizing_type]
87
102
  end
88
103
 
89
- def group_gravity_opts
90
- gravity = extract_and_trim_nils(:gravity, :gravity_x, :gravity_y)
91
-
92
- self[:gravity] = gravity unless gravity[0].nil?
93
- end
94
-
95
- def group_extend_opts
96
- extend_opts =
97
- extract_and_trim_nils(:extend, :extend_gravity, :extend_gravity_x, :extend_gravity_y)
98
-
99
- return if extend_opts[0].nil?
100
- return self[:extend] = 0 if extend_opts[0].zero?
101
-
102
- self[:extend] = extend_opts
103
- end
104
-
105
104
  def group_adjust_opts
106
- return unless values_at(:brightness, :contrast, :saturation).count { |o| !o.nil? } > 1
105
+ return if self[:adjust]
106
+ return unless values_at(:brightness, :contrast, :saturation).count { |o| o } > 1
107
107
 
108
108
  self[:adjust] = extract_and_trim_nils(:brightness, :contrast, :saturation)
109
109
  end
110
110
 
111
- def group_watermark_opts
112
- watermark = extract_and_trim_nils(
113
- :watermark_opacity,
114
- :watermark_position,
115
- :watermark_x_offset,
116
- :watermark_y_offset,
117
- :watermark_scale,
118
- )
119
-
120
- self[:watermark] = watermark unless watermark[0].nil?
121
- end
122
-
123
- def group_trim_opts
124
- trim = extract_and_trim_nils(
125
- :trim_threshold,
126
- :trim_color,
127
- :trim_equal_hor,
128
- :trim_equal_ver,
129
- )
130
-
131
- self[:trim] = trim unless trim[0].nil?
132
- end
133
-
134
- def encode_style
135
- return if self[:style].nil?
136
- self[:style] = Base64.urlsafe_encode64(self[:style]).tr("=", "")
137
- end
138
-
139
- def encode_watermark_url
140
- return if self[:watermark_url].nil?
141
- self[:watermark_url] = Base64.urlsafe_encode64(self[:watermark_url]).tr("=", "")
142
- end
143
-
144
111
  def extract_and_trim_nils(*keys)
145
- trim_nils(keys.map { |k| delete(k) })
146
- end
147
-
148
- def trim_nils(value)
149
- value.delete_at(-1) while !value.empty? && value[-1].nil?
150
- value
112
+ keys.map { |k| delete(k) }.trim!
151
113
  end
152
114
  end
153
115
  end