httpthumbnailer 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/Gemfile +4 -3
- data/Gemfile.lock +12 -12
- data/README.md +242 -68
- data/Rakefile +8 -2
- data/VERSION +1 -1
- data/bin/httpthumbnailer +35 -7
- data/lib/httpthumbnailer/error_reporter.rb +4 -2
- data/lib/httpthumbnailer/ownership.rb +54 -0
- data/lib/httpthumbnailer/plugin.rb +87 -0
- data/lib/httpthumbnailer/plugin/thumbnailer.rb +22 -427
- data/lib/httpthumbnailer/plugin/thumbnailer/service.rb +163 -0
- data/lib/httpthumbnailer/plugin/thumbnailer/service/built_in_plugins.rb +134 -0
- data/lib/httpthumbnailer/plugin/thumbnailer/service/images.rb +295 -0
- data/lib/httpthumbnailer/plugin/thumbnailer/service/magick.rb +208 -0
- data/lib/httpthumbnailer/thumbnail_specs.rb +130 -37
- data/lib/httpthumbnailer/thumbnailer.rb +29 -11
- metadata +30 -81
- data/.rspec +0 -1
- data/features/httpthumbnailer.feature +0 -24
- data/features/identify.feature +0 -31
- data/features/step_definitions/httpthumbnailer_steps.rb +0 -159
- data/features/support/env.rb +0 -106
- data/features/support/test-large.jpg +0 -0
- data/features/support/test-transparent.png +0 -0
- data/features/support/test.jpg +0 -0
- data/features/support/test.png +0 -0
- data/features/support/test.txt +0 -1
- data/features/thumbnail.feature +0 -269
- data/features/thumbnails.feature +0 -158
- data/httpthumbnailer.gemspec +0 -121
- data/load_test/extralarge.jpg +0 -0
- data/load_test/large.jpg +0 -0
- data/load_test/large.png +0 -0
- data/load_test/load_test-374846090-1.1.0-rc1-identify-only.csv +0 -3
- data/load_test/load_test-374846090-1.1.0-rc1.csv +0 -11
- data/load_test/load_test-cd9679c.csv +0 -10
- data/load_test/load_test-v0.3.1.csv +0 -10
- data/load_test/load_test.jmx +0 -733
- data/load_test/medium.jpg +0 -0
- data/load_test/small.jpg +0 -0
- data/load_test/soak_test-ac0c6bcbe5e-broken-libjpeg-tatoos.csv +0 -11
- data/load_test/soak_test-cd9679c.csv +0 -10
- data/load_test/soak_test-f98334a-tatoos.csv +0 -11
- data/load_test/soak_test.jmx +0 -754
- data/load_test/tiny.jpg +0 -0
- data/load_test/v0.0.13-loading.csv +0 -7
- data/load_test/v0.0.13.csv +0 -7
- data/load_test/v0.0.14-no-optimization.csv +0 -10
- data/load_test/v0.0.14.csv +0 -10
- data/spec/image_processing_spec.rb +0 -148
- data/spec/plugin_thumbnailer_spec.rb +0 -318
- data/spec/spec_helper.rb +0 -14
- data/spec/support/square_even.png +0 -0
- data/spec/support/square_odd.png +0 -0
- data/spec/support/test_image.rb +0 -16
- data/spec/thumbnail_specs_spec.rb +0 -43
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
require 'httpthumbnailer/ownership'
|
3
|
+
|
4
|
+
### WARNING: 'raise' is overwritten with an image operation method; use Kernel::raise instead!
|
5
|
+
class Magick::Image
|
6
|
+
include Ownership
|
7
|
+
include ClassLogging
|
8
|
+
include PerfStats
|
9
|
+
|
10
|
+
# use this on image before doing in-place (like composite!) edit so borrowed images are not changed
|
11
|
+
def get_for_inplace
|
12
|
+
get do |image|
|
13
|
+
if image.borrowed?
|
14
|
+
yield image.copy
|
15
|
+
else
|
16
|
+
yield image
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.new_8bit(width, height, background_color = "none")
|
22
|
+
Magick::Image.new(width, height) {
|
23
|
+
self.depth = 8
|
24
|
+
begin
|
25
|
+
self.background_color = background_color
|
26
|
+
rescue ArgumentError
|
27
|
+
Kernel::raise Plugin::Thumbnailer::InvalidColorNameError.new(background_color)
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_on_background(background_color, width = nil, height = nil, float_x = 0.5, float_y = 0.5)
|
33
|
+
# default to image size
|
34
|
+
width ||= self.columns
|
35
|
+
height ||= self.rows
|
36
|
+
|
37
|
+
# make sure we have enough background to fit image on top of it
|
38
|
+
width = self.columns if width < self.columns
|
39
|
+
height = self.rows if height < self.rows
|
40
|
+
|
41
|
+
self.class.new_8bit(width, height, background_color).get do |background|
|
42
|
+
background.composite!(self, *background.float_to_offset(self.columns, self.rows, float_x, float_y), Magick::OverCompositeOp)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# non coping version
|
47
|
+
def resize_to_fill(width, height = nil, float_x = 0.5, float_y = 0.5)
|
48
|
+
# default to square
|
49
|
+
height ||= width
|
50
|
+
|
51
|
+
return if width == columns and height == rows
|
52
|
+
|
53
|
+
scale = [width / columns.to_f, height / rows.to_f].max
|
54
|
+
|
55
|
+
get do |image| # this will comsume (destory) self just after resize
|
56
|
+
image.resize((scale * columns).ceil, (scale * rows).ceil)
|
57
|
+
end.get do |image|
|
58
|
+
next if width == image.width and height == image.height
|
59
|
+
image.crop(*image.float_to_offset(width, height, float_x, float_y), width, height, true)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# make rotate not to change image.page to avoid WTF moments
|
64
|
+
alias :rotate_orig :rotate
|
65
|
+
def rotate(*args)
|
66
|
+
out = rotate_orig(*args)
|
67
|
+
out.page = Magick::Rectangle.new(out.columns, out.rows, 0, 0)
|
68
|
+
out
|
69
|
+
end
|
70
|
+
|
71
|
+
def downsample(f)
|
72
|
+
sample(columns / f, rows / f)
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_downsample_factor(max_width, max_height, factor = 1)
|
76
|
+
new_factor = factor * 2
|
77
|
+
if columns / new_factor > max_width * 2 and rows / new_factor > max_height * 2
|
78
|
+
find_downsample_factor(max_width, max_height, factor * 2)
|
79
|
+
else
|
80
|
+
factor
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def float_to_offset(float_width, float_height, float_x = 0.5, float_y = 0.5)
|
85
|
+
base_width = self.columns
|
86
|
+
base_height = self.rows
|
87
|
+
|
88
|
+
x = ((base_width - float_width) * float_x).ceil
|
89
|
+
y = ((base_height - float_height) * float_y).ceil
|
90
|
+
|
91
|
+
x = 0 if x < 0
|
92
|
+
x = (base_width - float_width) if x > (base_width - float_width)
|
93
|
+
|
94
|
+
y = 0 if y < 0
|
95
|
+
y = (base_height - float_height) if y > (base_height - float_height)
|
96
|
+
|
97
|
+
[x, y]
|
98
|
+
end
|
99
|
+
|
100
|
+
def pixelate_region(x, y, w, h, size)
|
101
|
+
factor = 1.0 / size
|
102
|
+
|
103
|
+
# what happens here
|
104
|
+
# 1. get an required box cut form the image
|
105
|
+
# 2. resize it down by fracto
|
106
|
+
# 3. resize it back up by factor
|
107
|
+
# 4. since we may end up with bigger image (due to size of the pixelate pixel) crop it to required size again
|
108
|
+
# 5. composite over the original image in required position
|
109
|
+
|
110
|
+
crop(x, y, w, h, true).get do |work_space|
|
111
|
+
work_space.sample((factor * w).ceil, (factor * h).ceil)
|
112
|
+
end.get do |image|
|
113
|
+
image.sample(size)
|
114
|
+
end.get do |image|
|
115
|
+
image.crop(0, 0 , w, h, true)
|
116
|
+
end.get do |image|
|
117
|
+
get_for_inplace do |orig|
|
118
|
+
orig.composite!(image, x, y, Magick::OverCompositeOp)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def blur_region(x, y, w, h, radius, sigma)
|
124
|
+
# NOTE: we need to have bigger region to blure then the final regios to prevent edge artifacts
|
125
|
+
# TODO: how do I calculate margin better? See: https://github.com/trevor/ImageMagick/blob/82d683349c7a6adc977f6f638f1b340e01bf0ea9/branches/ImageMagick-6.5.9/magick/gem.c#L787
|
126
|
+
margin = [3, radius, sigma].max.ceil
|
127
|
+
|
128
|
+
mx = x - margin
|
129
|
+
my = y - margin
|
130
|
+
mw = w + margin
|
131
|
+
mh = h + margin
|
132
|
+
|
133
|
+
# limit the box with margin to available image size
|
134
|
+
mx = 0 if mx < 0
|
135
|
+
my = 0 if my < 0
|
136
|
+
mw = width - mx if mw + mx > width
|
137
|
+
mh = height - my if mh + my > height
|
138
|
+
|
139
|
+
crop(mx, my, mw, mh, true).get do |work_space|
|
140
|
+
work_space.blur_image(radius, sigma)
|
141
|
+
end.get do |blur|
|
142
|
+
blur.crop(x - mx, y - my, w, h, true)
|
143
|
+
end.get do |blur|
|
144
|
+
get_for_inplace do |orig|
|
145
|
+
orig.composite!(blur, x, y, Magick::OverCompositeOp)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def render_rectangle(x, y, w, h, color)
|
151
|
+
get_for_inplace do |orig|
|
152
|
+
gc = Magick::Draw.new
|
153
|
+
gc.fill = color
|
154
|
+
gc.rectangle(x, y, x + w - 1, y + h - 1)
|
155
|
+
gc.draw(orig)
|
156
|
+
orig
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# helpers
|
161
|
+
|
162
|
+
def with_background_color(color)
|
163
|
+
if color
|
164
|
+
was = self.background_color
|
165
|
+
begin
|
166
|
+
begin
|
167
|
+
self.background_color = color
|
168
|
+
rescue ArgumentError
|
169
|
+
Kernel::raise Plugin::Thumbnailer::InvalidColorNameError.new(color)
|
170
|
+
end
|
171
|
+
yield
|
172
|
+
ensure
|
173
|
+
self.background_color = was
|
174
|
+
end
|
175
|
+
else
|
176
|
+
yield
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def rel_to_px_pos(x, y)
|
181
|
+
[(x * columns).floor, (y * rows).floor]
|
182
|
+
end
|
183
|
+
|
184
|
+
def rel_to_px_dim(width, height)
|
185
|
+
[(width * columns).ceil, (height * rows).ceil]
|
186
|
+
end
|
187
|
+
|
188
|
+
def rel_to_px_box(x, y, width, height)
|
189
|
+
[*rel_to_px_pos(x, y), *rel_to_px_dim(width, height)]
|
190
|
+
end
|
191
|
+
|
192
|
+
def rel_to_diagonal(v)
|
193
|
+
(v * diagonal).ceil
|
194
|
+
end
|
195
|
+
|
196
|
+
def width
|
197
|
+
columns
|
198
|
+
end
|
199
|
+
|
200
|
+
def height
|
201
|
+
rows
|
202
|
+
end
|
203
|
+
|
204
|
+
def diagonal
|
205
|
+
@_diag ||= Math.sqrt(width ** 2 + height ** 2).ceil
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class ThumbnailSpecs < Array
|
2
|
-
def self.
|
2
|
+
def self.from_string(specs)
|
3
3
|
ts = ThumbnailSpecs.new
|
4
4
|
specs.split('/').each do |spec|
|
5
|
-
ts << ThumbnailSpec.
|
5
|
+
ts << ThumbnailSpec.from_string(spec)
|
6
6
|
end
|
7
7
|
ts
|
8
8
|
end
|
@@ -23,61 +23,154 @@ class ThumbnailSpecs < Array
|
|
23
23
|
end
|
24
24
|
|
25
25
|
class ThumbnailSpec
|
26
|
-
class
|
27
|
-
|
28
|
-
|
29
|
-
super "missing argument in: #{spec}"
|
30
|
-
end
|
26
|
+
class InvalidFormatError < ArgumentError
|
27
|
+
def for_edit(name)
|
28
|
+
exception "#{message} for edit '#{name}'"
|
31
29
|
end
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
super "missing option key or value in: #{option}"
|
36
|
-
end
|
31
|
+
def in_spec(spec)
|
32
|
+
exception "#{message} in spec '#{spec}'"
|
37
33
|
end
|
34
|
+
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
36
|
+
class MissingArgumentError < InvalidFormatError
|
37
|
+
def initialize(argument)
|
38
|
+
super "missing #{argument} argument"
|
43
39
|
end
|
44
40
|
end
|
45
41
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
class InvalidArgumentValueError < InvalidFormatError
|
43
|
+
def initialize(name, value, reason)
|
44
|
+
super "#{name} value '#{value}' is not #{reason}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class MissingOptionKeyValuePairError < InvalidFormatError
|
49
|
+
def initialize(index)
|
50
|
+
super "missing key-value pair on position #{index + 1}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class MissingOptionKeyNameError < InvalidFormatError
|
55
|
+
def initialize(value)
|
56
|
+
super "missing option key name for value '#{value}'"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class MissingOptionKeyValueError < InvalidFormatError
|
61
|
+
def initialize(key)
|
62
|
+
super "missing option value for key '#{key}'"
|
63
|
+
end
|
52
64
|
end
|
53
65
|
|
54
|
-
|
55
|
-
|
56
|
-
|
66
|
+
class EditSpec
|
67
|
+
attr_reader :name, :args, :options
|
68
|
+
|
69
|
+
def self.from_string(string)
|
70
|
+
args = ThumbnailSpec.split_args(string)
|
71
|
+
args, options = ThumbnailSpec.partition_args_options(args)
|
72
|
+
name = args.shift
|
73
|
+
|
74
|
+
begin
|
75
|
+
options = ThumbnailSpec.parse_options(options)
|
76
|
+
rescue InvalidFormatError => error
|
77
|
+
raise error.for_edit(name)
|
78
|
+
end
|
79
|
+
new(name, args, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(name, args, options = {})
|
83
|
+
name.nil? or name.empty? and raise MissingArgumentError, 'edit name'
|
84
|
+
|
85
|
+
@name = name
|
86
|
+
@args = args
|
87
|
+
@options = options
|
88
|
+
end
|
57
89
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
90
|
+
def to_s
|
91
|
+
begin
|
92
|
+
[@name, *@args, *ThumbnailSpec.options_to_s(@options)].join(',')
|
93
|
+
rescue InvalidFormatError => error
|
94
|
+
raise error.for_edit(name)
|
95
|
+
end
|
63
96
|
end
|
97
|
+
end
|
98
|
+
|
99
|
+
attr_reader :method, :width, :height, :format, :options, :edits
|
100
|
+
|
101
|
+
def self.from_string(string)
|
102
|
+
edits = split_edits(string)
|
103
|
+
spec = edits.shift
|
104
|
+
args = split_args(spec)
|
105
|
+
method, width, height, format, *options = *args
|
106
|
+
|
107
|
+
options = parse_options(options)
|
108
|
+
edits = edits.map{|e| EditSpec.from_string(e)}
|
64
109
|
|
65
|
-
|
110
|
+
new(method, width, height, format, options, edits)
|
111
|
+
rescue InvalidFormatError => error
|
112
|
+
raise error.in_spec(string)
|
66
113
|
end
|
67
114
|
|
115
|
+
def initialize(method, width, height, format, options = {}, edits = [])
|
116
|
+
method.nil? or method.empty? and raise MissingArgumentError, 'method'
|
117
|
+
width.nil? or width.empty? and raise MissingArgumentError, 'width'
|
118
|
+
height.nil? or height.empty? and raise MissingArgumentError, 'height'
|
119
|
+
format.nil? or format.empty? and raise MissingArgumentError, 'format'
|
68
120
|
|
69
|
-
|
121
|
+
width !~ /^([0-9]+|input)$/ and raise InvalidArgumentValueError.new('width', width, "an integer or 'input'")
|
122
|
+
height !~ /^([0-9]+|input)$/ and raise InvalidArgumentValueError.new('height', height, "an integer or 'input'")
|
123
|
+
|
124
|
+
width = width == 'input' ? :input : width.to_i
|
125
|
+
height = height == 'input' ? :input : height.to_i
|
126
|
+
|
127
|
+
format = format == 'input' ? :input : format.upcase
|
128
|
+
|
129
|
+
@method = method
|
130
|
+
@width = width
|
131
|
+
@height = height
|
132
|
+
@format = format
|
133
|
+
@options = options
|
134
|
+
@edits = edits
|
135
|
+
end
|
70
136
|
|
71
137
|
def to_s
|
72
|
-
|
138
|
+
[[@method, @width, @height, @format, *self.class.options_to_s(@options)].join(','), *@edits.map(&:to_s)].join('!')
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.split_edits(string)
|
142
|
+
string.split('!')
|
73
143
|
end
|
74
144
|
|
75
|
-
|
145
|
+
def self.split_args(string)
|
146
|
+
string.split(',')
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.partition_args_options(args)
|
150
|
+
options = args.drop_while{|a| not a.include?(':')}
|
151
|
+
args = args.take_while{|a| not a.include?(':')}
|
152
|
+
[args, options]
|
153
|
+
end
|
76
154
|
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
|
155
|
+
def self.parse_options(options)
|
156
|
+
Hash[options.map.with_index do |pair, index|
|
157
|
+
pair.empty? and raise MissingOptionKeyValuePairError, index
|
158
|
+
pair.split(':', 2)
|
159
|
+
end].tap do |map|
|
160
|
+
map.each do |key, value|
|
161
|
+
key.nil? or key.empty? and raise MissingOptionKeyNameError, value
|
162
|
+
value.nil? or value.empty? and raise MissingOptionKeyValueError, key
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.options_to_s(options)
|
168
|
+
options.sort_by{|k,v| k}.map do |key, value|
|
169
|
+
raise MissingOptionKeyNameError, value if key.nil? or key.to_s.empty?
|
170
|
+
raise MissingOptionKeyValueError, key if value.nil? or value.to_s.empty?
|
171
|
+
key = key.to_s.gsub('_', '-') if key.kind_of? Symbol
|
172
|
+
"#{key}:#{value}"
|
173
|
+
end
|
81
174
|
end
|
82
175
|
end
|
83
176
|
|
@@ -2,6 +2,7 @@ require 'httpthumbnailer/plugin/thumbnailer'
|
|
2
2
|
require 'httpthumbnailer/thumbnail_specs'
|
3
3
|
|
4
4
|
class Thumbnailer < Controller
|
5
|
+
Plugin::Thumbnailer.logger = logger_for(Plugin::Thumbnailer)
|
5
6
|
self.plugin Plugin::Thumbnailer
|
6
7
|
|
7
8
|
self.define do
|
@@ -9,7 +10,7 @@ class Thumbnailer < Controller
|
|
9
10
|
opts[:limit_memory] = memory_limit
|
10
11
|
|
11
12
|
on put, 'thumbnail', /(.*)/ do |spec|
|
12
|
-
spec = ThumbnailSpec.
|
13
|
+
spec = ThumbnailSpec.from_string(spec)
|
13
14
|
log.info "thumbnailing image to single spec: #{spec}"
|
14
15
|
|
15
16
|
if settings[:optimization]
|
@@ -17,23 +18,32 @@ class Thumbnailer < Controller
|
|
17
18
|
opts[:max_height] = spec.height if spec.height.is_a? Integer
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
+
opts[:reload] = settings[:reload]
|
22
|
+
opts[:no_upscale_fix] = settings[:no_upscale_fix]
|
23
|
+
opts[:no_downsample] = settings[:no_downsample]
|
24
|
+
|
25
|
+
thumbnailer.load(req.body, opts) do |input_image|
|
21
26
|
log.info "original image loaded: #{input_image.mime_type}"
|
22
27
|
|
28
|
+
# take the values here since the input_image will be destroyed after thumbnail!
|
29
|
+
input_image_mime_type = input_image.mime_type
|
30
|
+
input_image_width = input_image.width
|
31
|
+
input_image_height = input_image.height
|
32
|
+
|
23
33
|
log.info "generating thumbnail: #{spec}"
|
24
|
-
input_image.thumbnail(spec) do |image|
|
34
|
+
input_image.thumbnail!(spec) do |image|
|
25
35
|
write 200, image.mime_type, image.data,
|
26
36
|
"X-Image-Width" => image.width,
|
27
37
|
"X-Image-Height" => image.height,
|
28
|
-
"X-Input-Image-Mime-Type" =>
|
29
|
-
"X-Input-Image-Width" =>
|
30
|
-
"X-Input-Image-Height" =>
|
38
|
+
"X-Input-Image-Mime-Type" => input_image_mime_type,
|
39
|
+
"X-Input-Image-Width" => input_image_width,
|
40
|
+
"X-Input-Image-Height" => input_image_height
|
31
41
|
end
|
32
42
|
end
|
33
43
|
end
|
34
44
|
|
35
45
|
on put, 'thumbnails', /(.*)/ do |specs|
|
36
|
-
thumbnail_specs = ThumbnailSpecs.
|
46
|
+
thumbnail_specs = ThumbnailSpecs.from_string(specs)
|
37
47
|
log.info "thumbnailing image to multiple specs: #{thumbnail_specs.join(', ')}"
|
38
48
|
|
39
49
|
if settings[:optimization]
|
@@ -41,7 +51,11 @@ class Thumbnailer < Controller
|
|
41
51
|
opts[:max_height] = thumbnail_specs.max_height
|
42
52
|
end
|
43
53
|
|
44
|
-
|
54
|
+
opts[:reload] = settings[:reload]
|
55
|
+
opts[:no_upscale_fix] = settings[:no_upscale_fix]
|
56
|
+
opts[:no_downsample] = settings[:no_downsample]
|
57
|
+
|
58
|
+
thumbnailer.load(req.body, opts) do |input_image|
|
45
59
|
log.info "original image loaded: #{input_image.mime_type}"
|
46
60
|
write_preamble 200,
|
47
61
|
"X-Input-Image-Mime-Type" => input_image.mime_type,
|
@@ -64,6 +78,10 @@ class Thumbnailer < Controller
|
|
64
78
|
write_error_part 400, error
|
65
79
|
when Plugin::Thumbnailer::ZeroSizedImageError
|
66
80
|
write_error_part 400, error
|
81
|
+
when Plugin::Thumbnailer::ThumbnailArgumentError
|
82
|
+
write_error_part 400, error
|
83
|
+
when Plugin::Thumbnailer::EditArgumentError
|
84
|
+
write_error_part 400, error
|
67
85
|
else
|
68
86
|
log.error "unhandled error while generating multipart response for thumbnail spec: #{spec}", error
|
69
87
|
write_error_part 500, error
|
@@ -83,11 +101,11 @@ class Thumbnailer < Controller
|
|
83
101
|
end
|
84
102
|
|
85
103
|
# disable preprocessing since we don't need them here
|
86
|
-
opts[:
|
87
|
-
opts[:
|
104
|
+
opts[:no_upscale_fix] = true
|
105
|
+
opts[:no_downsample] = true
|
88
106
|
|
89
107
|
# RMagick of v2.13.2 does not use ImageMagick's PingBlob so we have to actually load the image
|
90
|
-
thumbnailer.load(req.body, opts)
|
108
|
+
thumbnailer.load(req.body, opts) do |input_image|
|
91
109
|
mime_type = input_image.mime_type
|
92
110
|
log.info "image loaded and identified as: #{mime_type}"
|
93
111
|
write_json 200, {
|