Class: Image::LegacyImageParamTranslator
- Inherits:
-
Object
- Object
- Image::LegacyImageParamTranslator
- Defined in:
- app/services/image/legacy_image_param_translator.rb
Overview
This class translates params from the legacy format e.g /img/w_400,h_200/
to the imagekit format /img/?tr=w-400,h-200
LEGACY: THIS CODE IS FOR REFERENCE ONLY, DO NOT USE WITHOUT REFACTORING AND TESTING
Constant Summary collapse
- REGEXP =
e.g. /img/s_600x600%3E/cross-section-of-a-tile-floor-subfloor-electric-heating-roll-thinset-and-tile-flooring-79ecae.jpg
Updated to exclude ? from capture groups to handle URLs with query strings (e.g., ?utm_source=chatgpt.com) %r{\A/img/?([^/?]*)?/([^/?]*)\.([a-zA-Z]{3,4})(?:\?.*)?\z}- REGEXP_FULL_URL =
%r{https://\w{3}\.warmlyyours\.com/img/?([^/?]*)?/([^/?]*)\.([a-zA-Z]{3,4})}- REGEXP_FULL_URL_WITH_LOCALE =
%r{https://\w{3}\.warmlyyours\.com/\+\+locale\+\+/img/?([^/?]*)?/([^/?]*)\.([a-zA-Z]{3,4})}
Class Method Summary collapse
-
.to_transformations(processing_string, encode_format = nil, stored_file_format = nil) ⇒ Object
Returns translated transformations.
- .translate_from_args(**args) ⇒ Object
-
.translate_from_components(processing_string, file_id, requested_file_format, rack_env = nil) ⇒ Object
processing_string: a legacy processing string, e.g w_100,h_100 or s_300x300! file_id: the slug stored in our database requested_file_format: the file format requested.
-
.translate_from_path(path, rack_env = nil) ⇒ Object
Provide a path, e.g.
-
.translate_from_url(url, rack_env = nil) ⇒ Object
Provide a URL, e.g.
-
.translate_from_url_with_locale(url, rack_env = nil) ⇒ Object
Same as above but for urls that include locale }.
Class Method Details
.to_transformations(processing_string, encode_format = nil, stored_file_format = nil) ⇒ Object
Returns translated transformations
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 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 |
# File 'app/services/image/legacy_image_param_translator.rb', line 92 def self.to_transformations(processing_string, encode_format = nil, stored_file_format = nil) processing_string = Addressable::URI.unescape(processing_string.to_s).presence process_tokens = processing_string&.split(',') || [] transformations = [] width = nil height = nil crop_x = nil crop_y = nil crop_w = nil crop_h = nil rotate = nil background = nil border_x = nil border_y = nil blur = nil encode_format = encode_format&.downcase encode_format = 'jpeg' if encode_format == 'jpg' encode_format = nil unless %w[jpeg gif svg png webp jp2 avif].include?(encode_format) process_tokens.each do |instruction| # e.g. w_100 cmd_method, value = instruction.split('_') next if value.nil? # Skip malformed instructions without a value # Process sizing first case cmd_method when 's' # Size size = CGI.unescapeHTML(value) # Do we have a x ? unless size.index('x') # Add as a width size = "#{size}x" end width, height, modifier = size.scan(/(\d+)x(\d+)?(.?)/)[0] when 'w' # Width width = value.scan(/(\d+)/)[0][0].to_i when 'h' # Height height = value.scan(/(\d+)/)[0][0].to_i when 'p' # Percentage of original size value = value.scan(/(\d+)/)[0][0].to_f pf = (value / 100).round(2) width = pf height = pf when 'cx' # Crop start from X crop_x = value.scan(/(\d+)/)[0][0].to_i when 'cy' # Crop start from y crop_y = value.scan(/(\d+)/)[0][0].to_i when 'cw' # Crop width crop_w = value.scan(/(\d+)/)[0][0].to_i when 'ch' # Crop height crop_h = value.scan(/(\d+)/)[0][0].to_i when 'r' # Rotate the image in degrees rotate = value.scan(/(\d+)/)[0][0].to_i when 'bg' background = value when 'bx' border_x = value.scan(/(\d+)/)[0][0].to_i when 'by' border_y = value.scan(/(\d+)/)[0][0].to_i when 'bl' blur = value.to_b end # when 'c' # Don't cache the output, set cache headers to expire right away # cache_output = value.to_b # when 'o' # Don't optimize, including don't webp the file # optimize = value.to_b # when 'wp' # Don't webp the file for chrome (but still optimize) # webp = value.to_b # when 'd' # Send the file as content disposition attachment # download = value.to_b # when 't' # Thumbnail mode, will crop according to size with center of gravity # thumbnail = value.to_b end # End looping through commands # Crop/extract FIRST (operates on original image dimensions) transformations << { crop_mode: 'extract', x: crop_x, y: crop_y, width: crop_w, height: crop_h } if crop_x && crop_y && crop_w && crop_h # Then resize (operates on the cropped result, or original if no crop) if width && height transformations << { width: width, height: height, cm: 'pad_resize', bg: background || 'FFFFFF' } elsif width || height transformations << { width: width, height: height }.compact end # rotate transformations << { rotation: rotate } if rotate if border_x || border_y b = border_x || border_y bc = background || '555555' bs = "#{b.to_i}_#{bc}" transformations << { border: b, background: background } end transformations << { background: background } if background # Redundant to encode format if the stored format is the same transformations << { format: encode_format } if encode_format && (stored_file_format.nil? || stored_file_format != encode_format) transformations end |
.translate_from_args(**args) ⇒ Object
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'app/services/image/legacy_image_param_translator.rb', line 192 def self.translate_from_args(**args) args ||= {} transformations = [] # https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations # size does not exist so we have to extract width and height # sizing is done as a group, size will be taken as authoritative over width and height if args[:size].present? w, h = args[:size].split('x').map(&:presence) transformations << { width: w&.to_i, height: h&.to_i }.compact elsif args[:width] || args[:height] transformations << { width: args[:width]&.to_i, height: args[:height]&.to_i }.compact elsif args[:percentage] && args[:percentage].to_i > 0 && args[:percentage].to_i < 100 # In ik, this translates to a value between 0 and 1 for width and height pf = (args[:percentage].to_f / 100).round(2) transformations << { width: pf, height: pf } end # Cropping will apply after resizing # crop_x: nil, crop_y: nil, crop_w: nil, crop_h: nil if crop_x = args[:crop_x] && crop_y = args[:crop_y] && crop_w = args[:crop_w] && crop_h = args[:crop_h] transformations << { cm: 'extract', x: crop_x, y: crop_y, width: crop_w, height: crop_h } end if b = args[:border] || args[:borderx] || args[:bordery] # there's no concept of x and y so we'll just take the first one # border color is possible but we didn't use this so we'll default to, gray? # If you pass true we'll use a nominal value of 3 px b = 3 if b == true bc = args[:border_color] || args[:background] || '555555' bs = "#{b.to_i}_#{bc}" transformations << { b: bs } end if rt = args[:rotate] transformations << { rt: rt } end if format = args[:encode_format] transformations << { f: format } end transformations end |
.translate_from_components(processing_string, file_id, requested_file_format, rack_env = nil) ⇒ Object
processing_string: a legacy processing string, e.g w_100,h_100 or s_300x300!
file_id: the slug stored in our database
requested_file_format: the file format requested. Note that our image kit url will always refer to what is
stored
rack_env: the rack environment at time of request for logging additional info
39 40 41 42 43 44 45 46 47 48 49 50 51 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 79 80 81 82 83 84 85 86 87 88 89 |
# File 'app/services/image/legacy_image_param_translator.rb', line 39 def self.translate_from_components(processing_string, file_id, requested_file_format, rack_env = nil) begin img = Image.friendly.find(file_id) stored_file_format = img. rescue ActiveRecord::RecordNotFound extra_data = rack_env&.select { |key, value| key.include?('HTTP_') || key.include?('REQUEST_') } || {} extra_data['FILE_ID'] = file_id ErrorReporting.warning("Could not find image #{file_id}", extra_data) Rails.logger.warn('Could not find image', file_id: file_id) end requested_file_format = requested_file_format&.downcase transformations = to_transformations(processing_string, requested_file_format, stored_file_format) # Legacy crop coordinate safety check: # In the old image processing system, crop coordinates (cx, cy, cw, ch) were often # set via CropperJS against a scaled-down preview of the image, NOT the full-resolution # original. ImageKit's cm-extract applies coordinates to the full-resolution original, # which produces wrong results when the coordinates were set on a smaller version. # # Detection: if the crop area is <5% of the original image area, the coordinates # were almost certainly from a scaled-down preview and cannot be reliably applied # to the original. In that case, we drop the crop step and fall back to resize-only, # which shows the full image at the requested dimensions. if img && (orig_w = img.ik_width || img.try(:attachment_width)) && (orig_h = img.ik_height || img.try(:attachment_height)) transformations.reject! do |t| if t[:crop_mode] == 'extract' && t[:width] && t[:height] && orig_w > 0 && orig_h > 0 crop_area_ratio = (t[:width].to_f * t[:height]) / (orig_w * orig_h) if crop_area_ratio < 0.05 Rails.logger.info( "Legacy crop coordinates dropped (#{crop_area_ratio.round(4)} of original area)", file_id: file_id, crop: t.slice(:x, :y, :width, :height), original: { width: orig_w, height: orig_h } ) true end end end end # Use the actual ImageKit path from the asset record when available. # This is critical because most images are stored WITH file extensions in ImageKit # (e.g., /img/my-image-abc123.jpeg), but ik_file_name returns only the slug # (e.g., my-image-abc123). Using the slug alone produces a 404 on ImageKit. # Fall back to constructing a path from file_id + requested format for unknown images. ik_path = img&.ik_path || "/img/#{file_id}.#{requested_file_format}" # Use ImageKitFactory helper method to build URL ImageKitFactory.build_url(src: ik_path, transformations: transformations) end |
.translate_from_path(path, rack_env = nil) ⇒ Object
Provide a path, e.g. /img/w_100,h_100/image123.png and get a ik url back
Optionally you can specify force_jpeg: true to enforce jpeg when png is requested
29 30 31 32 |
# File 'app/services/image/legacy_image_param_translator.rb', line 29 def self.translate_from_path(path, rack_env = nil) processing_string, file_id, file_format = path.scan(REGEXP)&.first translate_from_components(processing_string, file_id, file_format, rack_env) end |
.translate_from_url(url, rack_env = nil) ⇒ Object
Provide a URL, e.g. https://ik.warmlyyours.com/img/image123.png?tr=w-100,h-100,cm-pad_resize,bg-FFFFFF:f-png and get a ik url back
Optionally you can specify force_jpeg: true to enforce jpeg when png is requested
16 17 18 19 |
# File 'app/services/image/legacy_image_param_translator.rb', line 16 def self.translate_from_url(url, rack_env = nil) processing_string, file_id, file_format = url.scan(REGEXP_FULL_URL)&.first translate_from_components(processing_string, file_id, file_format, rack_env) end |
.translate_from_url_with_locale(url, rack_env = nil) ⇒ Object
Same as above but for urls that include locale }
22 23 24 25 |
# File 'app/services/image/legacy_image_param_translator.rb', line 22 def self.translate_from_url_with_locale(url, rack_env = nil) processing_string, file_id, file_format = url.scan(REGEXP_FULL_URL_WITH_LOCALE)&.first translate_from_components(processing_string, file_id, file_format, rack_env) end |