Module: Imgwire::URLTransformations

Defined in:
lib/imgwire/url_transformations.rb

Overview

rubocop:disable Metrics/ModuleLength

Constant Summary collapse

RULES =
{
  'adjust' => %w[a adjust],
  'background' => %w[bg background],
  'background_alpha' => %w[bga background_alpha],
  'blur' => %w[bl blur],
  'brightness' => %w[br brightness],
  'chroma_subsampling' => %w[chroma_subsampling],
  'color_profile' => %w[cp icc color_profile],
  'colorize' => %w[col colorize],
  'contrast' => %w[co contrast],
  'crop' => %w[c crop],
  'dpi' => %w[dpi],
  'dpr' => %w[dpr],
  'duotone' => %w[dt duotone],
  'enlarge' => %w[el enlarge],
  'extend' => %w[ex extend],
  'extend_aspect_ratio' => %w[exar extend_ar extend_aspect_ratio],
  'flip' => %w[fl flip],
  'format' => %w[f format ext extension fm],
  'gradient' => %w[gr gradient],
  'gravity' => %w[g gravity],
  'height' => %w[h height],
  'hue' => %w[hu hue],
  'keep_copyright' => %w[kcr keep_copyright],
  'lightness' => %w[l lightness],
  'min-height' => %w[mh min_height min-height],
  'min-width' => %w[mw min_width min-width],
  'monochrome' => %w[mc monochrome],
  'negate' => %w[neg negate],
  'normalize' => %w[norm normalise normalize],
  'padding' => %w[pd padding],
  'pixelate' => %w[pix pixelate],
  'progressive' => %w[progressive],
  'quality' => %w[q quality],
  'resizing_algorithm' => %w[ra resizing_algorithm],
  'resizing_type' => %w[resizing_type],
  'rotate' => %w[rot rotate],
  'saturation' => %w[sa saturation],
  'sharpen' => %w[sh sharpen],
  'strip_color_profile' => %w[scp strip_color_profile],
  'strip_metadata' => %w[sm strip_metadata strip],
  'watermark' => %w[wm watermark],
  'watermark_position' => %w[wmp watermark_offset watermark_position],
  'watermark_rotate' => %w[wmr wm_rot watermark_rotate],
  'watermark_shadow' => %w[wmsh watermark_shadow],
  'watermark_size' => %w[wms watermark_size],
  'watermark_text' => %w[wmt watermark_text],
  'watermark_url' => %w[wmu watermark_url],
  'width' => %w[w width],
  'zoom' => %w[z zoom]
}.freeze
COLOR_PROFILES =
%w[srgb rgb16 cmyk keep preserve].freeze
CHROMA_SUBSAMPLING_VALUES =
%w[4:2:0 4:4:4 auto].freeze
FORMATS =
%w[auto jpg jpeg png webp avif gif tiff].freeze
FLIP_VALUES =
%w[vertical horizontal both].freeze
RESIZING_ALGORITHMS =
%w[nearest cubic mitchell lanczos2 lanczos3].freeze
RESIZING_TYPES =
%w[cover contain fill inside outside].freeze
RESIZING_TYPE_ALIASES =
{
  'auto' => 'inside',
  'fill-down' => 'inside',
  'fit' => 'inside',
  'force' => 'fill'
}.freeze
GRAVITY_VALUES =
%w[
  ce center north south east west northeast northwest southeast southwest attention entropy
].freeze
GRAVITY_ALIASES =
{
  'n' => 'north',
  'no' => 'north',
  's' => 'south',
  'so' => 'south',
  'e' => 'east',
  'ea' => 'east',
  'w' => 'west',
  'we' => 'west',
  'ne' => 'northeast',
  'noea' => 'northeast',
  'se' => 'southeast',
  'soea' => 'southeast',
  'nw' => 'northwest',
  'nowe' => 'northwest',
  'sw' => 'southwest',
  'sowe' => 'southwest',
  'ce:sm' => 'attention'
}.freeze

Class Method Summary collapse

Class Method Details

.build_query(options) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/imgwire/url_transformations.rb', line 99

def build_query(options)
  normalized_options = normalize_top_level_keys(options)

  RULES.each_with_object([]) do |(canonical, aliases), present|
    matches = aliases.filter_map do |name|
      key = name.to_sym
      [key, normalized_options[key]] if normalized_options.key?(key)
    end

    next if matches.empty?
    raise ArgumentError, "Duplicate transformation rule: #{canonical}" if matches.length > 1

    value = normalize_rule(canonical, matches.first.last)
    present << [canonical, value] unless value.nil?
  end
end

.camelize_hash_keys(hash) ⇒ Object



904
905
906
907
908
# File 'lib/imgwire/url_transformations.rb', line 904

def camelize_hash_keys(hash)
  hash.each_with_object({}) do |(key, value), result|
    result[camelize_key(key)] = value.is_a?(Hash) ? camelize_hash_keys(value) : value
  end
end

.camelize_key(key) ⇒ Object



910
911
912
# File 'lib/imgwire/url_transformations.rb', line 910

def camelize_key(key)
  key.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
end

.compact_json_object(input_hash, fields) ⇒ Object



891
892
893
894
895
896
# File 'lib/imgwire/url_transformations.rb', line 891

def compact_json_object(input_hash, fields)
  return unless fields.values.any?
  return if fields.any? { |key, item| field?(input_hash, key) && item.nil? }

  JSON.generate(fields.compact)
end

.contiguous_segments(required, optional) ⇒ Object



723
724
725
726
727
728
729
730
731
# File 'lib/imgwire/url_transformations.rb', line 723

def contiguous_segments(required, optional)
  return if required.any?(&:nil?)

  last_present = optional.rindex { |segment| !segment.nil? }
  selected_optional = last_present.nil? ? [] : optional[0..last_present]
  return if selected_optional.any?(&:nil?)

  (required + selected_optional).join(':')
end

.field(hash, *names) ⇒ Object



700
701
702
703
704
705
706
# File 'lib/imgwire/url_transformations.rb', line 700

def field(hash, *names)
  names.each do |name|
    return hash[name] if hash.key?(name)
  end

  nil
end

.field?(hash, *names) ⇒ Boolean

Returns:

  • (Boolean)


708
709
710
# File 'lib/imgwire/url_transformations.rb', line 708

def field?(hash, *names)
  names.any? { |name| hash.key?(name) }
end

.format_number(number) ⇒ Object



815
816
817
818
819
# File 'lib/imgwire/url_transformations.rb', line 815

def format_number(number)
  return number.to_i.to_s if number == number.to_i

  number.to_s
end

.json_object(hash) ⇒ Object



898
899
900
901
902
# File 'lib/imgwire/url_transformations.rb', line 898

def json_object(hash)
  return if hash.empty?

  JSON.generate(camelize_hash_keys(hash))
end

.json_object_string?(value) ⇒ Boolean

Returns:

  • (Boolean)


878
879
880
881
882
883
884
885
# File 'lib/imgwire/url_transformations.rb', line 878

def json_object_string?(value)
  string = normalize_non_empty_string(value)
  return false unless string&.start_with?('{')

  JSON.parse(string).is_a?(Hash)
rescue JSON::ParserError
  false
end

.normalize_adjust(value) ⇒ Object



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/imgwire/url_transformations.rb', line 201

def normalize_adjust(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  if hash
    brightness = normalize_number(field(hash, 'brightness'))
    saturation = normalize_number(field(hash, 'saturation'))
    color = normalize_number(field(hash, 'color'))

    return contiguous_segments([brightness], [saturation, color])
  end

  segments = split_segments(value, min: 1, max: 3)
  return unless segments

  brightness = normalize_number(segments[0])
  saturation = normalize_number(segments[1]) if segments.length > 1
  color = normalize_number(segments[2]) if segments.length > 2

  contiguous_segments([brightness], [saturation, color].first(segments.length - 1))
end

.normalize_boolean_flag(value) ⇒ Object



733
734
735
# File 'lib/imgwire/url_transformations.rb', line 733

def normalize_boolean_flag(value)
  parse_boolean(value) == true ? 'true' : nil
end

.normalize_boolean_or_auto(value) ⇒ Object



737
738
739
740
741
742
743
744
# File 'lib/imgwire/url_transformations.rb', line 737

def normalize_boolean_or_auto(value)
  return 'auto' if normalize_string(value) == 'auto'

  boolean = parse_boolean(value)
  return 'true' if boolean == true

  'false' if boolean == false
end

.normalize_color(value) ⇒ Object



835
836
837
838
839
840
841
842
843
# File 'lib/imgwire/url_transformations.rb', line 835

def normalize_color(value)
  string = normalize_non_empty_string(value)
  return unless string

  string = string.delete_prefix('#')
  return string.downcase if string.match?(/\A(?:[[:xdigit:]]{3}|[[:xdigit:]]{6}|[[:xdigit:]]{8})\z/)

  string if rgb_color?(string)
end

.normalize_color_list(value) ⇒ Object



857
858
859
860
861
862
863
864
865
# File 'lib/imgwire/url_transformations.rb', line 857

def normalize_color_list(value)
  colors = value.is_a?(Array) ? value : normalize_non_empty_string(value)&.split(',', -1)
  return unless colors&.length&.>= 2

  normalized = colors.map { |color| normalize_color(color) }
  return if normalized.any?(&:nil?)

  normalized.join(',')
end

.normalize_contrast(value) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/imgwire/url_transformations.rb', line 223

def normalize_contrast(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  if hash
    multiplier = normalize_number(field(hash, 'multiplier'))
    pivot = normalize_number(field(hash, 'pivot'))

    return contiguous_segments([multiplier], [pivot])
  end

  segments = split_segments(value, min: 1, max: 2)
  return unless segments

  multiplier = normalize_number(segments[0])
  pivot = normalize_number(segments[1]) if segments.length > 1

  contiguous_segments([multiplier], [pivot].first(segments.length - 1))
end

.normalize_crop(value) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/imgwire/url_transformations.rb', line 243

def normalize_crop(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return normalize_crop_hash(hash) if hash

  segments = split_segments(value, min: 2, max: 5)
  return unless [2, 3, 4, 5].include?(segments&.length)

  if segments.length <= 3
    width = normalize_integer(segments[0], min: 1)
    height = normalize_integer(segments[1], min: 1)
    gravity = normalize_gravity(segments[2]) if segments.length == 3

    return contiguous_segments([width, height], [gravity].first(segments.length - 2))
  end

  x = normalize_integer(segments[0])
  y = normalize_integer(segments[1])
  width = normalize_integer(segments[2], min: 1)
  height = normalize_integer(segments[3], min: 1)
  gravity = normalize_gravity(segments[4]) if segments.length == 5

  contiguous_segments([x, y, width, height], [gravity].first(segments.length - 4))
end

.normalize_crop_hash(hash) ⇒ Object



269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/imgwire/url_transformations.rb', line 269

def normalize_crop_hash(hash)
  x = normalize_integer(field(hash, 'x'))
  y = normalize_integer(field(hash, 'y'))
  width = normalize_integer(field(hash, 'width', 'w'), min: 1)
  height = normalize_integer(field(hash, 'height', 'h'), min: 1)
  gravity = normalize_gravity(field(hash, 'gravity', 'g'))

  if field?(hash, 'x') || field?(hash, 'y')
    contiguous_segments([x, y, width, height], [gravity])
  else
    contiguous_segments([width, height], [gravity])
  end
end

.normalize_duotone(value) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/imgwire/url_transformations.rb', line 283

def normalize_duotone(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  if hash
    shadow = normalize_color(field(hash, 'shadowColor', 'shadow_color', 'shadow'))
    highlight = normalize_color(field(hash, 'highlightColor', 'highlight_color', 'highlight'))

    return contiguous_segments([shadow, highlight], [])
  end

  segments = split_segments(value, min: 2, max: 2)
  return unless segments

  shadow = normalize_color(segments[0])
  highlight = normalize_color(segments[1])

  contiguous_segments([shadow, highlight], [])
end

.normalize_enum(value, values, aliases: {}) ⇒ Object



821
822
823
824
825
826
# File 'lib/imgwire/url_transformations.rb', line 821

def normalize_enum(value, values, aliases: {})
  string = normalize_string(value)
  return aliases[string] if aliases.key?(string)

  string if values.include?(string)
end

.normalize_extend(value) ⇒ Object



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/imgwire/url_transformations.rb', line 303

def normalize_extend(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return normalize_extend_hash(hash) if hash

  segments = split_segments(value, min: 1, max: 5)
  return unless segments

  top = normalize_integer(segments[0])
  right = normalize_integer(segments[1]) if segments.length > 1
  bottom = normalize_integer(segments[2]) if segments.length > 2
  left = normalize_integer(segments[3]) if segments.length > 3
  background = normalize_color(segments[4]) if segments.length > 4

  contiguous_segments([top], [right, bottom, left, background].first(segments.length - 1))
end

.normalize_extend_aspect_ratio(value) ⇒ Object



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/imgwire/url_transformations.rb', line 341

def normalize_extend_aspect_ratio(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  if hash
    ratio = normalize_number(field(hash, 'ratio'), min: 0.000001)
    return ratio if ratio

    width = normalize_integer(field(hash, 'width', 'w'), min: 1)
    height = normalize_integer(field(hash, 'height', 'h'), min: 1)

    return contiguous_segments([width, height], [])
  end

  segments = split_segments(value, min: 1, max: 2)
  return unless segments

  return normalize_number(segments[0], min: 0.000001) if segments.length == 1

  width = normalize_integer(segments[0], min: 1)
  height = normalize_integer(segments[1], min: 1)

  contiguous_segments([width, height], [])
end

.normalize_extend_hash(hash) ⇒ Object



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/imgwire/url_transformations.rb', line 321

def normalize_extend_hash(hash)
  top = normalize_integer(field(hash, 'top'))
  right = normalize_integer(field(hash, 'right'))
  bottom = normalize_integer(field(hash, 'bottom'))
  left = normalize_integer(field(hash, 'left'))
  background = normalize_color(field(hash, 'background', 'bg'))

  compact_json = compact_json_object(
    hash,
    'top' => top,
    'right' => right,
    'bottom' => bottom,
    'left' => left,
    'background' => background
  )
  return unless compact_json

  contiguous_segments([top], [right, bottom, left, background]) || compact_json
end

.normalize_flip(value) ⇒ Object



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/imgwire/url_transformations.rb', line 366

def normalize_flip(value)
  hash = value_hash(value)
  if hash
    horizontal = parse_boolean(field(hash, 'horizontal'))
    vertical = parse_boolean(field(hash, 'vertical'))
    return if horizontal.nil? && vertical.nil?

    return 'both' if horizontal && vertical
    return 'horizontal' if horizontal
    return 'vertical' if vertical

    return
  end

  string = normalize_string(value)
  return string if FLIP_VALUES.include?(string)

  segments = split_segments(value, min: 2, max: 2)
  return unless segments

  horizontal = parse_boolean(segments[0])
  vertical = parse_boolean(segments[1])
  return if horizontal.nil? || vertical.nil?

  "#{horizontal}:#{vertical}"
end

.normalize_gradient(value) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/imgwire/url_transformations.rb', line 393

def normalize_gradient(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return normalize_gradient_hash(hash) if hash

  segments = split_segments(value, min: 1, max: 4)
  return unless segments

  colors = normalize_color_list(segments[0])
  angle = normalize_number(segments[1]) if segments.length > 1
  opacity = normalize_number(segments[2], min: 0, max: 1) if segments.length > 2
  blend = normalize_non_empty_string(segments[3]) if segments.length > 3

  contiguous_segments([colors], [angle, opacity, blend].first(segments.length - 1))
end

.normalize_gradient_hash(hash) ⇒ Object



410
411
412
413
414
415
416
417
# File 'lib/imgwire/url_transformations.rb', line 410

def normalize_gradient_hash(hash)
  colors = normalize_color_list(field(hash, 'colors'))
  angle = normalize_number(field(hash, 'angle'))
  opacity = normalize_number(field(hash, 'opacity'), min: 0, max: 1)
  blend = normalize_non_empty_string(field(hash, 'blend'))

  contiguous_segments([colors], [angle, opacity, blend])
end

.normalize_gravity(value) ⇒ Object



828
829
830
831
832
833
# File 'lib/imgwire/url_transformations.rb', line 828

def normalize_gravity(value)
  string = normalize_string(value)
  return GRAVITY_ALIASES[string] if GRAVITY_ALIASES.key?(string)

  string if GRAVITY_VALUES.include?(string)
end

.normalize_integer(value, min: nil, max: nil) ⇒ Object



768
769
770
771
772
773
774
775
# File 'lib/imgwire/url_transformations.rb', line 768

def normalize_integer(value, min: nil, max: nil)
  integer = parse_integer(value)
  return if integer.nil?
  return if min && integer < min
  return if max && integer > max

  integer.to_s
end

.normalize_json_string(value) ⇒ Object



887
888
889
# File 'lib/imgwire/url_transformations.rb', line 887

def normalize_json_string(value)
  normalize_non_empty_string(value)
end

.normalize_monochrome(value) ⇒ Object



419
420
421
422
423
424
425
# File 'lib/imgwire/url_transformations.rb', line 419

def normalize_monochrome(value)
  boolean = parse_boolean(value)
  return 'true' if boolean == true
  return if boolean == false

  normalize_color(value)
end

.normalize_negate(value) ⇒ Object



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/imgwire/url_transformations.rb', line 427

def normalize_negate(value)
  hash = value_hash(value)
  if hash
    alpha = parse_boolean(field(hash, 'alpha'))
    return unless [true, false].include?(alpha)

    return "alpha:#{alpha}"
  end

  boolean = parse_boolean(value)
  return 'true' if boolean == true
  return if boolean == false

  segments = split_segments(value, min: 2, max: 2)
  return unless segments&.first == 'alpha'

  alpha = parse_boolean(segments[1])
  return unless [true, false].include?(alpha)

  "alpha:#{alpha}"
end

.normalize_non_empty_string(value) ⇒ Object



867
868
869
870
871
872
# File 'lib/imgwire/url_transformations.rb', line 867

def normalize_non_empty_string(value)
  string = value.to_s.strip
  return if string.empty?

  string
end

.normalize_normalize(value) ⇒ Object



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/imgwire/url_transformations.rb', line 449

def normalize_normalize(value)
  hash = value_hash(value)
  if hash
    lower = normalize_number(field(hash, 'lower'))
    upper = normalize_number(field(hash, 'upper'))

    return contiguous_segments([lower, upper], [])
  end

  boolean = parse_boolean(value)
  return 'true' if boolean == true
  return if boolean == false

  segments = split_segments(value, min: 2, max: 2)
  return unless segments

  lower = normalize_number(segments[0])
  upper = normalize_number(segments[1])

  contiguous_segments([lower, upper], [])
end

.normalize_number(value, min: nil, max: nil) ⇒ Object



792
793
794
795
796
797
798
799
# File 'lib/imgwire/url_transformations.rb', line 792

def normalize_number(value, min: nil, max: nil)
  number = parse_number(value)
  return if number.nil?
  return if min && number < min
  return if max && number > max

  format_number(number)
end

.normalize_padding(value) ⇒ Object



471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/imgwire/url_transformations.rb', line 471

def normalize_padding(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return normalize_padding_hash(hash) if hash

  segments = split_segments(value, min: 1, max: 4)
  return unless segments

  normalized = segments.map { |segment| normalize_integer(segment) }
  return if normalized.any?(&:nil?)

  normalized.join(':')
end

.normalize_padding_hash(hash) ⇒ Object



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/imgwire/url_transformations.rb', line 486

def normalize_padding_hash(hash)
  all = normalize_integer(field(hash, 'all'))
  return all if all

  x = normalize_integer(field(hash, 'x'))
  y = normalize_integer(field(hash, 'y'))
  return contiguous_segments([x, y], []) if x || y

  top = normalize_integer(field(hash, 'top'))
  right = normalize_integer(field(hash, 'right'))
  bottom = normalize_integer(field(hash, 'bottom'))
  left = normalize_integer(field(hash, 'left'))

  compact_json = compact_json_object(
    hash,
    'top' => top,
    'right' => right,
    'bottom' => bottom,
    'left' => left
  )
  return unless compact_json

  contiguous_segments([top, right, bottom, left], []) || compact_json
end

.normalize_quality(value) ⇒ Object



511
512
513
514
515
# File 'lib/imgwire/url_transformations.rb', line 511

def normalize_quality(value)
  return 'auto' if normalize_string(value) == 'auto'

  normalize_integer(value, min: 1, max: 100)
end

.normalize_rotate(value) ⇒ Object



517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/imgwire/url_transformations.rb', line 517

def normalize_rotate(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  if hash
    angle = normalize_number(field(hash, 'angle'))
    background = normalize_color(field(hash, 'background', 'bg'))

    return contiguous_segments([angle], [background])
  end

  segments = split_segments(value, min: 1, max: 2)
  return unless segments

  angle = normalize_number(segments[0])
  background = normalize_color(segments[1]) if segments.length > 1

  contiguous_segments([angle], [background].first(segments.length - 1))
end

.normalize_rule(canonical, value) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/imgwire/url_transformations.rb', line 116

def normalize_rule(canonical, value)
  case canonical
  when 'adjust'
    normalize_adjust(value)
  when 'background', 'colorize'
    normalize_color(value)
  when 'background_alpha'
    normalize_number(value, min: 0, max: 1)
  when 'blur'
    normalize_true_or_number(value, min: 0.3, max: 100)
  when 'brightness', 'lightness', 'saturation'
    normalize_number(value, min: 0.01, max: 10)
  when 'chroma_subsampling'
    normalize_enum(value, CHROMA_SUBSAMPLING_VALUES)
  when 'color_profile'
    normalize_enum(value, COLOR_PROFILES)
  when 'contrast'
    normalize_contrast(value)
  when 'crop'
    normalize_crop(value)
  when 'dpi'
    normalize_integer(value, min: 1, max: 600)
  when 'dpr'
    normalize_number(value, min: 0.01, max: 8)
  when 'duotone'
    normalize_duotone(value)
  when 'enlarge', 'keep_copyright', 'strip_color_profile', 'strip_metadata'
    normalize_boolean_flag(value)
  when 'extend'
    normalize_extend(value)
  when 'extend_aspect_ratio'
    normalize_extend_aspect_ratio(value)
  when 'flip'
    normalize_flip(value)
  when 'format'
    normalize_enum(value, FORMATS)
  when 'gradient'
    normalize_gradient(value)
  when 'gravity'
    normalize_gravity(value)
  when 'height', 'min-height', 'min-width', 'width'
    normalize_integer(value, min: 1, max: 8192)
  when 'hue'
    normalize_number(value)
  when 'monochrome'
    normalize_monochrome(value)
  when 'negate'
    normalize_negate(value)
  when 'normalize'
    normalize_normalize(value)
  when 'padding'
    normalize_padding(value)
  when 'pixelate'
    normalize_integer(value, min: 2, max: 256)
  when 'progressive'
    normalize_boolean_or_auto(value)
  when 'quality'
    normalize_quality(value)
  when 'resizing_algorithm'
    normalize_enum(value, RESIZING_ALGORITHMS)
  when 'resizing_type'
    normalize_enum(value, RESIZING_TYPES, aliases: RESIZING_TYPE_ALIASES)
  when 'rotate', 'watermark_rotate'
    normalize_rotate(value)
  when 'sharpen'
    normalize_sharpen(value)
  when 'watermark'
    normalize_watermark(value)
  when 'watermark_position'
    normalize_watermark_position(value)
  when 'watermark_shadow'
    normalize_watermark_shadow(value)
  when 'watermark_size'
    normalize_watermark_size(value)
  when 'watermark_text'
    normalize_text_or_json_object(value)
  when 'watermark_url'
    normalize_watermark_url(value)
  when 'zoom'
    normalize_zoom(value)
  end
rescue ArgumentError, TypeError
  nil
end

.normalize_sharpen(value) ⇒ Object



537
538
539
540
541
542
543
544
# File 'lib/imgwire/url_transformations.rb', line 537

def normalize_sharpen(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return json_object(hash) if hash

  normalize_true_or_number(value)
end

.normalize_string(value) ⇒ Object



874
875
876
# File 'lib/imgwire/url_transformations.rb', line 874

def normalize_string(value)
  normalize_non_empty_string(value)&.downcase
end

.normalize_text_or_json_object(value) ⇒ Object



654
655
656
657
658
659
660
661
# File 'lib/imgwire/url_transformations.rb', line 654

def normalize_text_or_json_object(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return json_object(hash) if hash

  normalize_non_empty_string(value)
end

.normalize_top_level_keys(hash) ⇒ Object



690
691
692
# File 'lib/imgwire/url_transformations.rb', line 690

def normalize_top_level_keys(hash)
  hash.to_h.transform_keys(&:to_sym)
end

.normalize_true_or_number(value, min: nil, max: nil) ⇒ Object



760
761
762
763
764
765
766
# File 'lib/imgwire/url_transformations.rb', line 760

def normalize_true_or_number(value, min: nil, max: nil)
  boolean = parse_boolean(value)
  return 'true' if boolean == true
  return if boolean == false

  normalize_number(value, min: min, max: max)
end

.normalize_watermark(value) ⇒ Object



546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/imgwire/url_transformations.rb', line 546

def normalize_watermark(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return normalize_watermark_hash(hash) if hash

  segments = split_segments(value, min: 1, max: 4)
  return unless segments

  image_id = normalize_non_empty_string(segments[0])
  gravity = normalize_gravity(segments[1]) if segments.length > 1
  x = normalize_integer(segments[2]) if segments.length > 2
  y = normalize_integer(segments[3]) if segments.length > 3

  contiguous_segments([image_id], [gravity, x, y].first(segments.length - 1))
end

.normalize_watermark_hash(hash) ⇒ Object



563
564
565
566
567
568
569
570
# File 'lib/imgwire/url_transformations.rb', line 563

def normalize_watermark_hash(hash)
  image_id = normalize_non_empty_string(field(hash, 'image_id', 'imageId', 'id'))
  gravity = normalize_gravity(field(hash, 'gravity', 'g'))
  x = normalize_integer(field(hash, 'x'))
  y = normalize_integer(field(hash, 'y'))

  contiguous_segments([image_id], [gravity, x, y])
end

.normalize_watermark_position(value) ⇒ Object



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
# File 'lib/imgwire/url_transformations.rb', line 572

def normalize_watermark_position(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return normalize_watermark_position_hash(hash) if hash

  segments = split_segments(value, min: 1, max: 5)
  return unless segments

  gravity = normalize_gravity(segments[0])
  x = normalize_integer(segments[1]) if segments.length > 1
  y = normalize_integer(segments[2]) if segments.length > 2
  opacity = normalize_number(segments[3], min: 0, max: 1) if segments.length > 3
  blend = normalize_non_empty_string(segments[4]) if segments.length > 4

  contiguous_segments([gravity], [x, y, opacity, blend].first(segments.length - 1))
end

.normalize_watermark_position_hash(hash) ⇒ Object



590
591
592
593
594
595
596
597
598
# File 'lib/imgwire/url_transformations.rb', line 590

def normalize_watermark_position_hash(hash)
  gravity = normalize_gravity(field(hash, 'gravity', 'g'))
  x = normalize_integer(field(hash, 'x'))
  y = normalize_integer(field(hash, 'y'))
  opacity = normalize_number(field(hash, 'opacity'), min: 0, max: 1)
  blend = normalize_non_empty_string(field(hash, 'blend'))

  contiguous_segments([gravity], [x, y, opacity, blend])
end

.normalize_watermark_shadow(value) ⇒ Object



600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
# File 'lib/imgwire/url_transformations.rb', line 600

def normalize_watermark_shadow(value)
  return normalize_json_string(value) if json_object_string?(value)

  boolean = parse_boolean(value)
  return 'true' if boolean == true
  return if boolean == false

  hash = value_hash(value)
  return normalize_watermark_shadow_hash(hash) if hash

  segments = split_segments(value, min: 1, max: 4)
  return unless segments

  color = normalize_color(segments[0])
  blur = normalize_number(segments[1]) if segments.length > 1
  x = normalize_integer(segments[2]) if segments.length > 2
  y = normalize_integer(segments[3]) if segments.length > 3

  contiguous_segments([color], [blur, x, y].first(segments.length - 1))
end

.normalize_watermark_shadow_hash(hash) ⇒ Object



621
622
623
624
625
626
627
628
# File 'lib/imgwire/url_transformations.rb', line 621

def normalize_watermark_shadow_hash(hash)
  color = normalize_color(field(hash, 'color'))
  blur = normalize_number(field(hash, 'blur'))
  x = normalize_integer(field(hash, 'x'))
  y = normalize_integer(field(hash, 'y'))

  contiguous_segments([color], [blur, x, y])
end

.normalize_watermark_size(value) ⇒ Object



630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
# File 'lib/imgwire/url_transformations.rb', line 630

def normalize_watermark_size(value)
  return normalize_json_string(value) if json_object_string?(value)

  hash = value_hash(value)
  return normalize_watermark_size_hash(hash) if hash

  segments = split_segments(value, min: 1, max: 3)
  return unless segments

  width = normalize_integer(segments[0], min: 1)
  height = normalize_integer(segments[1], min: 1) if segments.length > 1
  scale = normalize_number(segments[2], min: 0.000001) if segments.length > 2

  contiguous_segments([width], [height, scale].first(segments.length - 1))
end

.normalize_watermark_size_hash(hash) ⇒ Object



646
647
648
649
650
651
652
# File 'lib/imgwire/url_transformations.rb', line 646

def normalize_watermark_size_hash(hash)
  width = normalize_integer(field(hash, 'width', 'w'), min: 1)
  height = normalize_integer(field(hash, 'height', 'h'), min: 1)
  scale = normalize_number(field(hash, 'scale'), min: 0.000001)

  contiguous_segments([width], [height, scale])
end

.normalize_watermark_url(value) ⇒ Object



663
664
665
666
667
668
669
670
# File 'lib/imgwire/url_transformations.rb', line 663

def normalize_watermark_url(value)
  string = normalize_non_empty_string(value)
  return unless string

  return Base64.strict_encode64(string) if valid_https_url?(string)

  string if valid_base64_https_url?(string)
end

.normalize_zoom(value) ⇒ Object



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
# File 'lib/imgwire/url_transformations.rb', line 672

def normalize_zoom(value)
  hash = value_hash(value)
  if hash
    factor = normalize_number(field(hash, 'factor'), min: 0.000001)
    gravity = normalize_gravity(field(hash, 'gravity', 'g'))

    return contiguous_segments([factor], [gravity])
  end

  segments = split_segments(value, min: 1, max: 2)
  return unless segments

  factor = normalize_number(segments[0], min: 0.000001)
  gravity = normalize_gravity(segments[1]) if segments.length > 1

  contiguous_segments([factor], [gravity].first(segments.length - 1))
end

.parse_boolean(value) ⇒ Object



746
747
748
749
750
751
752
753
754
755
756
757
758
# File 'lib/imgwire/url_transformations.rb', line 746

def parse_boolean(value)
  return true if value == true
  return false if value == false
  return true if value == 1
  return false if value.is_a?(Numeric) && value.zero?

  case normalize_string(value)
  when 'true', '1'
    true
  when 'false', '0'
    false
  end
end

.parse_integer(value) ⇒ Object



777
778
779
780
781
782
783
784
785
786
787
788
789
790
# File 'lib/imgwire/url_transformations.rb', line 777

def parse_integer(value)
  return value if value.is_a?(Integer)

  if value.is_a?(Float)
    return unless value.finite? && value == value.to_i

    return value.to_i
  end

  string = normalize_non_empty_string(value)
  return unless string&.match?(/\A[+-]?\d+\z/)

  Integer(string)
end

.parse_number(value) ⇒ Object



801
802
803
804
805
806
807
808
809
810
811
812
813
# File 'lib/imgwire/url_transformations.rb', line 801

def parse_number(value)
  return value.to_f if value.is_a?(Numeric) && value.to_f.finite?

  string = normalize_non_empty_string(value)
  return unless string

  number = Float(string)
  return unless number.finite?

  number
rescue ArgumentError, TypeError
  nil
end

.rgb_color?(string) ⇒ Boolean

Returns:

  • (Boolean)


845
846
847
848
849
850
851
852
853
854
855
# File 'lib/imgwire/url_transformations.rb', line 845

def rgb_color?(string)
  segments = string.split(':', -1)
  return false unless [3, 4].include?(segments.length)

  red, green, blue = segments.first(3).map { |segment| parse_integer(segment) }
  return false unless [red, green, blue].all? { |part| part&.between?(0, 255) }
  return true if segments.length == 3

  alpha = parse_number(segments[3])
  alpha&.between?(0, 1)
end

.split_segments(value, min:, max:) ⇒ Object



712
713
714
715
716
717
718
719
720
721
# File 'lib/imgwire/url_transformations.rb', line 712

def split_segments(value, min:, max:)
  string = normalize_non_empty_string(value)
  return unless string

  segments = string.split(':', -1)
  return unless segments.length.between?(min, max)
  return if segments.any?(&:empty?)

  segments
end

.valid_base64_https_url?(value) ⇒ Boolean

Returns:

  • (Boolean)


921
922
923
924
925
926
# File 'lib/imgwire/url_transformations.rb', line 921

def valid_base64_https_url?(value)
  decoded = Base64.strict_decode64(value)
  valid_https_url?(decoded)
rescue ArgumentError
  false
end

.valid_https_url?(value) ⇒ Boolean

Returns:

  • (Boolean)


914
915
916
917
918
919
# File 'lib/imgwire/url_transformations.rb', line 914

def valid_https_url?(value)
  uri = URI.parse(value)
  uri.is_a?(URI::HTTPS) && uri.host && !uri.host.empty?
rescue URI::InvalidURIError
  false
end

.value_hash(value) ⇒ Object



694
695
696
697
698
# File 'lib/imgwire/url_transformations.rb', line 694

def value_hash(value)
  return unless value.respond_to?(:to_hash)

  value.to_hash.transform_keys(&:to_s)
end