dynamic_image 2.0.21 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7183bc59ab57bc659f3d38e0c2bd1fa250689d1e4647dfcc68e0eddceb4c2260
4
- data.tar.gz: 882038f115a0e2d78684f02a19672138d8a68a3e36af0ac74d736a7d4056083a
3
+ metadata.gz: fd1d723cd97a91199630354e541d2a740aa41bae8ccb07554c1e723096a1b6dc
4
+ data.tar.gz: b4f5a8bf26b69da95543cb8885f5add2b72504ea88f83c4097bc1c25a13c622f
5
5
  SHA512:
6
- metadata.gz: c2a6a1f35e3458693ae0d0c5b9eaca4fd5ddcd70164e070f67f80236723befc57d677d0de673bf94afb0416458cde7bc04648f7bd512d5682984af6ccb4adeae
7
- data.tar.gz: b6ca975270f79fb9100aaeb1d2083f30adf7196c77783f663b741f84144c49bc6b42338ba220b050fca4637746f57328adff2d88f9fc9b48c829ff08d9157e72
6
+ metadata.gz: e2ba7e0be3c929f9da2b1c7311ae7a246341f081cc8057e8293417a8a73634ec5844d0ce7a691c3d5c771d99924b311c59a2a9b9bf2e2baae84675a3967a8614
7
+ data.tar.gz: '08cfc8124f2d6e2bd9d505315b1af7ed87e42ae4ff497a88fafd240e9594943721a6f72e7f196d5dffb1a43a1c8489e9b1b427e4042e149aedcf21a22f0506ba'
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  [![Version](https://img.shields.io/gem/v/dynamic_image.svg?style=flat)](https://rubygems.org/gems/dynamic_image)
2
- [![Build Status](https://travis-ci.org/elektronaut/dynamic_image.svg?branch=master)](https://travis-ci.org/elektronaut/dynamic_image)
2
+ ![Build](https://github.com/elektronaut/dynamic_image/workflows/Build/badge.svg)
3
3
  [![Code Climate](https://codeclimate.com/github/elektronaut/dynamic_image/badges/gpa.svg)](https://codeclimate.com/github/elektronaut/dynamic_image)
4
4
  [![Code Climate](https://codeclimate.com/github/elektronaut/dynamic_image/badges/coverage.svg)](https://codeclimate.com/github/elektronaut/dynamic_image)
5
5
  [![Inline docs](http://inch-ci.org/github/elektronaut/dynamic_image.svg)](http://inch-ci.org/github/elektronaut/dynamic_image)
@@ -31,6 +31,7 @@ and enumeration attacks.
31
31
  * Rails 5
32
32
  * Ruby 2.4+
33
33
  * ImageMagick command line tools
34
+ * ExifTool
34
35
 
35
36
  ## Documentation
36
37
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "mini_exiftool"
3
4
  require "mini_magick"
4
5
  require "dis"
5
6
  require "vector2d"
@@ -12,11 +13,12 @@ require "dynamic_image/errors"
12
13
  require "dynamic_image/helper"
13
14
  require "dynamic_image/image_reader"
14
15
  require "dynamic_image/image_sizing"
16
+ require "dynamic_image/jobs"
15
17
  require "dynamic_image/metadata"
16
18
  require "dynamic_image/model"
17
19
  require "dynamic_image/processed_image"
18
20
  require "dynamic_image/routing"
19
21
 
20
22
  module DynamicImage
21
- cattr_accessor :digest_verifier
23
+ cattr_accessor :digest_verifier, :process_later_limit
22
24
  end
@@ -8,7 +8,7 @@ module DynamicImage
8
8
 
9
9
  module ClassMethods
10
10
  def belongs_to_image(name, scope = nil, **options)
11
- belongs_to(name, scope, options)
11
+ belongs_to(name, scope, **options)
12
12
 
13
13
  define_method "#{name}=" do |new_image|
14
14
  if new_image.present? && !new_image.is_a?(DynamicImage::Model)
@@ -44,13 +44,21 @@ module DynamicImage
44
44
  private
45
45
 
46
46
  def cache_expiration_header
47
- expires_in 30.days, public: true
47
+ expires_in 30.days, public: true if response.status == 200
48
48
  end
49
49
 
50
50
  def find_record
51
51
  @record = model.find(params[:id])
52
52
  end
53
53
 
54
+ def process_later?(processed_image, size)
55
+ return false unless DynamicImage.process_later_limit
56
+
57
+ image_size = processed_image.record.size.x * processed_image.record.size.y
58
+ image_size > DynamicImage.process_later_limit &&
59
+ !processed_image.find_variant(size)
60
+ end
61
+
54
62
  def render_image(options)
55
63
  return unless stale?(@record)
56
64
 
@@ -59,8 +67,8 @@ module DynamicImage
59
67
  render(file: File.join(File.dirname(__FILE__), "templates/show"),
60
68
  layout: false, locals: { options: options })
61
69
  end
62
- format.any(:gif, :jpeg, :png, :tiff, :webp) do
63
- send_image(DynamicImage::ProcessedImage.new(@record, options))
70
+ format.any(:gif, :jpeg, :jpg, :png, :tiff, :webp) do
71
+ send_image(@record, options)
64
72
  end
65
73
  end
66
74
  end
@@ -69,7 +77,7 @@ module DynamicImage
69
77
  return unless stale?(@record)
70
78
 
71
79
  respond_to do |format|
72
- format.any(:gif, :jpeg, :png, :tiff, :webp) do
80
+ format.any(:gif, :jpeg, :jpg, :png, :tiff, :webp) do
73
81
  send_data(@record.data,
74
82
  filename: filename,
75
83
  content_type: @record.content_type,
@@ -82,12 +90,17 @@ module DynamicImage
82
90
  params[:format]
83
91
  end
84
92
 
85
- def send_image(processed_image)
86
- send_data(
87
- processed_image.cropped_and_resized(requested_size),
88
- content_type: processed_image.content_type,
89
- disposition: "inline"
90
- )
93
+ def send_image(image, options)
94
+ processed_image = DynamicImage::ProcessedImage.new(image, options)
95
+ if process_later?(processed_image, requested_size)
96
+ DynamicImage::Jobs::CreateVariant
97
+ .perform_later(image, options, requested_size.to_s)
98
+ head 503, retry_after: 10
99
+ else
100
+ send_data(processed_image.cropped_and_resized(requested_size),
101
+ content_type: processed_image.content_type,
102
+ disposition: "inline")
103
+ end
91
104
  end
92
105
 
93
106
  def verify_signed_params
@@ -31,7 +31,7 @@ module DynamicImage
31
31
  ActiveSupport.on_load(:active_record) do
32
32
  send :include, DynamicImage::BelongsTo
33
33
  end
34
- ActionDispatch::Routing::Mapper.send :include, DynamicImage::Routing
34
+ ActionDispatch::Routing::Mapper.include DynamicImage::Routing
35
35
 
36
36
  ActionDispatch::ExceptionWrapper.rescue_responses.merge!(
37
37
  "DynamicImage::Errors::InvalidSignature" => :unauthorized
@@ -21,6 +21,12 @@ module DynamicImage
21
21
  @data = data
22
22
  end
23
23
 
24
+ def exif
25
+ raise DynamicImage::Errors::InvalidHeader unless valid_header?
26
+
27
+ MiniExiftool.new(string_io)
28
+ end
29
+
24
30
  def read
25
31
  raise DynamicImage::Errors::InvalidHeader unless valid_header?
26
32
 
@@ -39,7 +45,11 @@ module DynamicImage
39
45
  private
40
46
 
41
47
  def file_header
42
- @file_header ||= StringIO.new(@data, "rb").read(8)
48
+ @file_header ||= string_io.read(8)
49
+ end
50
+
51
+ def string_io
52
+ StringIO.new(@data, "rb")
43
53
  end
44
54
  end
45
55
  end
@@ -149,8 +149,8 @@ module DynamicImage
149
149
  @uncropped
150
150
  end
151
151
 
152
- def vector(x, y)
153
- Vector2d.new(x, y)
152
+ def vector(width, height)
153
+ Vector2d.new(width, height)
154
154
  end
155
155
  end
156
156
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dynamic_image/jobs/create_variant"
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DynamicImage
4
+ module Jobs
5
+ # = Create variant
6
+ #
7
+ # Creates an image variant.
8
+ class CreateVariant < ActiveJob::Base
9
+ queue_as :dis
10
+
11
+ def perform(record, options, size)
12
+ size_v = Vector2d.parse(size)
13
+ DynamicImage::ProcessedImage.new(record, options)
14
+ .find_or_create_variant(size_v)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -82,8 +82,8 @@ module DynamicImage
82
82
 
83
83
  private
84
84
 
85
- def vector(x, y)
86
- Vector2d.new(x, y)
85
+ def vector(width, height)
86
+ Vector2d.new(width, height)
87
87
  end
88
88
  end
89
89
  end
@@ -6,6 +6,8 @@ module DynamicImage
6
6
  # Handles all processing of images. Takes an instance of
7
7
  # +DynamicImage::Model+ as argument.
8
8
  class ProcessedImage
9
+ attr_reader :record
10
+
9
11
  def initialize(record, options = {})
10
12
  @record = record
11
13
  @uncropped = options[:uncropped] ? true : false
@@ -40,10 +42,24 @@ module DynamicImage
40
42
  find_or_create_variant(size).data
41
43
  end
42
44
 
45
+ # Find or create a variant with the given size.
46
+ def find_or_create_variant(size)
47
+ find_variant(size) || create_variant(size)
48
+ rescue ActiveRecord::RecordNotUnique
49
+ find_variant(size)
50
+ end
51
+
52
+ # Find a variant with the given size.
53
+ def find_variant(size)
54
+ return nil unless record.persisted?
55
+
56
+ record.variants.find_by(variant_params(size))
57
+ end
58
+
43
59
  # Normalizes the image.
44
60
  #
45
61
  # * Applies EXIF rotation
46
- # * CMYK images are converted to sRGB
62
+ # * Converts to sRGB
47
63
  # * Strips metadata
48
64
  # * Optimizes GIFs
49
65
  # * Performs format conversion if the requested format is different
@@ -59,7 +75,7 @@ module DynamicImage
59
75
  process_data do |image|
60
76
  image.combine_options do |combined|
61
77
  combined.auto_orient
62
- combined.colorspace("sRGB") if needs_colorspace_conversion?
78
+ convert_to_srgb(image, combined)
63
79
  yield(combined) if block_given?
64
80
  optimize(combined)
65
81
  end
@@ -70,27 +86,36 @@ module DynamicImage
70
86
  private
71
87
 
72
88
  def coalesced(image)
73
- return image unless gif?
89
+ gif? ? DynamicImage::ImageReader.new(image.coalesce.to_blob).read : image
90
+ end
74
91
 
75
- DynamicImage::ImageReader.new(image.coalesce.to_blob).read
92
+ def convert_to_srgb(image, combined)
93
+ if image.data["profiles"].present? &&
94
+ exif.colorspacedata&.strip&.downcase == record.colorspace
95
+ combined.profile(srgb_profile)
96
+ end
97
+ combined.colorspace("sRGB") if record.cmyk?
98
+ end
99
+
100
+ def create_variant(size)
101
+ record.variants.create(
102
+ variant_params(size).merge(filename: record.filename,
103
+ content_type: content_type,
104
+ data: crop_and_resize(size))
105
+ )
76
106
  end
77
107
 
78
108
  def crop_and_resize(size)
79
109
  normalized do |image|
80
- if record.cropped? || size != record.size
81
- image.crop(image_sizing.crop_geometry_string(size))
82
- image.resize(size)
83
- end
110
+ next unless record.cropped? || size != record.size
111
+
112
+ image.crop(image_sizing.crop_geometry_string(size))
113
+ image.resize(size)
84
114
  end
85
115
  end
86
116
 
87
- def find_or_create_variant(size)
88
- record.variants.find_by(variant_params(size)) ||
89
- record.variants.create(
90
- variant_params(size).merge(filename: record.filename,
91
- content_type: content_type,
92
- data: crop_and_resize(size))
93
- )
117
+ def exif
118
+ @exif ||= DynamicImage::ImageReader.new(record.data).exif
94
119
  end
95
120
 
96
121
  def format
@@ -102,7 +127,7 @@ module DynamicImage
102
127
  end
103
128
 
104
129
  def jpeg?
105
- content_type == "image/jpeg" || content_type == "image/jpeg"
130
+ content_type == "image/jpeg"
106
131
  end
107
132
 
108
133
  def image_sizing
@@ -110,10 +135,6 @@ module DynamicImage
110
135
  DynamicImage::ImageSizing.new(record, uncropped: @uncropped)
111
136
  end
112
137
 
113
- def needs_colorspace_conversion?
114
- record.cmyk?
115
- end
116
-
117
138
  def needs_format_conversion?
118
139
  format != record_format
119
140
  end
@@ -133,15 +154,9 @@ module DynamicImage
133
154
  result
134
155
  end
135
156
 
136
- attr_reader :record
137
-
138
157
  def record_format
139
- { "image/bmp" => "BMP",
140
- "image/png" => "PNG",
141
- "image/gif" => "GIF",
142
- "image/jpeg" => "JPEG",
143
- "image/pjpeg" => "JPEG",
144
- "image/tiff" => "TIFF",
158
+ { "image/bmp" => "BMP", "image/png" => "PNG", "image/gif" => "GIF",
159
+ "image/jpeg" => "JPEG", "image/pjpeg" => "JPEG", "image/tiff" => "TIFF",
145
160
  "image/webp" => "WEBP" }[record.content_type]
146
161
  end
147
162
 
@@ -149,15 +164,16 @@ module DynamicImage
149
164
  raise DynamicImage::Errors::InvalidImage unless record.valid?
150
165
  end
151
166
 
167
+ def srgb_profile
168
+ File.join(File.dirname(__FILE__), "profiles/sRGB_ICC_v4_Appearance.icc")
169
+ end
170
+
152
171
  def variant_params(size)
153
172
  crop_size, crop_start = image_sizing.crop_geometry(size)
154
173
 
155
- { width: size.x.round,
156
- height: size.y.round,
157
- crop_width: crop_size.x,
158
- crop_height: crop_size.y,
159
- crop_start_x: crop_start.x,
160
- crop_start_y: crop_start.y,
174
+ { width: size.x.round, height: size.y.round,
175
+ crop_width: crop_size.x, crop_height: crop_size.y,
176
+ crop_start_x: crop_start.x, crop_start_y: crop_start.y,
161
177
  format: format }
162
178
  end
163
179
  end
@@ -13,8 +13,9 @@ module DynamicImage
13
13
  options = {
14
14
  path: "#{resource_name}/:digest(/:size)",
15
15
  constraints: { size: /\d+x\d+/ },
16
- only: [:show]
16
+ only: %i[show]
17
17
  }.merge(options)
18
+
18
19
  resources resource_name, options do
19
20
  get :uncropped, on: :member
20
21
  get :original, on: :member
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DynamicImage
4
- VERSION = "2.0.21"
4
+ VERSION = "2.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic_image
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.21
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-04 00:00:00.000000000 Z
11
+ date: 2020-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dis
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 1.0.6
33
+ - !ruby/object:Gem::Dependency
34
+ name: mini_exiftool
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.10'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.10'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: mini_magick
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -78,6 +92,20 @@ dependencies:
78
92
  - - ">="
79
93
  - !ruby/object:Gem::Version
80
94
  version: 2.2.1
95
+ - !ruby/object:Gem::Dependency
96
+ name: rails-controller-testing
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
81
109
  - !ruby/object:Gem::Dependency
82
110
  name: rspec-rails
83
111
  requirement: !ruby/object:Gem::Requirement
@@ -92,6 +120,20 @@ dependencies:
92
120
  - - "~>"
93
121
  - !ruby/object:Gem::Version
94
122
  version: 3.7.0
123
+ - !ruby/object:Gem::Dependency
124
+ name: simplecov
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: 0.17.1
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: 0.17.1
95
137
  - !ruby/object:Gem::Dependency
96
138
  name: sqlite3
97
139
  requirement: !ruby/object:Gem::Requirement
@@ -127,6 +169,8 @@ files:
127
169
  - lib/dynamic_image/helper.rb
128
170
  - lib/dynamic_image/image_reader.rb
129
171
  - lib/dynamic_image/image_sizing.rb
172
+ - lib/dynamic_image/jobs.rb
173
+ - lib/dynamic_image/jobs/create_variant.rb
130
174
  - lib/dynamic_image/metadata.rb
131
175
  - lib/dynamic_image/model.rb
132
176
  - lib/dynamic_image/model/dimensions.rb
@@ -134,6 +178,7 @@ files:
134
178
  - lib/dynamic_image/model/validations.rb
135
179
  - lib/dynamic_image/model/variants.rb
136
180
  - lib/dynamic_image/processed_image.rb
181
+ - lib/dynamic_image/profiles/sRGB_ICC_v4_Appearance.icc
137
182
  - lib/dynamic_image/routing.rb
138
183
  - lib/dynamic_image/templates/show.html.erb
139
184
  - lib/dynamic_image/version.rb
@@ -142,7 +187,7 @@ homepage: https://github.com/elektronaut/dynamic_image
142
187
  licenses:
143
188
  - MIT
144
189
  metadata: {}
145
- post_install_message:
190
+ post_install_message:
146
191
  rdoc_options: []
147
192
  require_paths:
148
193
  - lib
@@ -158,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
203
  version: '0'
159
204
  requirements: []
160
205
  rubygems_version: 3.1.2
161
- signing_key:
206
+ signing_key:
162
207
  specification_version: 4
163
208
  summary: Rails plugin that simplifies image uploading and processing
164
209
  test_files: []