morandi 0.12.1 → 0.99.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -2
- data/README.md +9 -21
- data/ext/gdk_pixbuf_cairo/extconf.rb +124 -0
- data/ext/gdk_pixbuf_cairo/gdk_pixbuf_cairo.c +260 -0
- data/ext/morandi_native/extconf.rb +121 -0
- data/ext/morandi_native/filter.h +229 -0
- data/ext/morandi_native/gamma.h +48 -0
- data/ext/morandi_native/mask.h +75 -0
- data/ext/morandi_native/morandi_native.c +1126 -0
- data/ext/morandi_native/rotate.h +79 -0
- data/ext/morandi_native/tint.h +72 -0
- data/lib/gdk_pixbuf_cairo.so +0 -0
- data/lib/morandi/cairo_ext.rb +56 -0
- data/lib/morandi/crop_utils.rb +95 -0
- data/lib/morandi/errors.rb +12 -0
- data/lib/morandi/image_operation.rb +31 -0
- data/lib/morandi/image_processor.rb +186 -180
- data/lib/morandi/operation/colourify.rb +45 -0
- data/lib/morandi/operation/image_border.rb +108 -0
- data/lib/morandi/operation/straighten.rb +41 -0
- data/lib/morandi/pixbuf_ext.rb +20 -0
- data/lib/morandi/profiled_pixbuf.rb +18 -71
- data/lib/morandi/redeye.rb +35 -47
- data/lib/morandi/srgb_conversion.rb +42 -0
- data/lib/morandi/version.rb +3 -1
- data/lib/morandi.rb +42 -12
- data/lib/morandi_native.so +0 -0
- metadata +57 -116
- data/.gitignore +0 -18
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/Gemfile +0 -4
- data/Rakefile +0 -1
- data/lib/morandi/image-ops.rb +0 -307
- data/lib/morandi/utils.rb +0 -136
- data/morandi.gemspec +0 -33
- data/sample/sample.jpg +0 -0
- data/spec/morandi_spec.rb +0 -208
- data/spec/spec_helper.rb +0 -19
data/lib/morandi/image-ops.rb
DELETED
@@ -1,307 +0,0 @@
|
|
1
|
-
require 'pango'
|
2
|
-
require 'colorscore'
|
3
|
-
|
4
|
-
module Morandi
|
5
|
-
class ImageOp
|
6
|
-
class << self
|
7
|
-
def new_from_hash(hash)
|
8
|
-
op = allocate()
|
9
|
-
hash.each_pair do |key,val|
|
10
|
-
op.instance_variable_set("@#{key}", val) if op.respond_to?(key.intern)
|
11
|
-
end
|
12
|
-
op
|
13
|
-
end
|
14
|
-
end
|
15
|
-
def initialize()
|
16
|
-
end
|
17
|
-
def priority
|
18
|
-
100
|
19
|
-
end
|
20
|
-
end
|
21
|
-
class Crop < ImageOp
|
22
|
-
attr_accessor :area
|
23
|
-
def initialize(area=nil)
|
24
|
-
super()
|
25
|
-
@area = area
|
26
|
-
end
|
27
|
-
def constrain(val,min,max)
|
28
|
-
if val < min
|
29
|
-
min
|
30
|
-
elsif val > max
|
31
|
-
max
|
32
|
-
else
|
33
|
-
val
|
34
|
-
end
|
35
|
-
end
|
36
|
-
def call(image, pixbuf)
|
37
|
-
if @area and (not @area.width.zero?) and (not @area.height.zero?)
|
38
|
-
# NB: Cheap - fast & shares memory
|
39
|
-
GdkPixbuf::Pixbuf.new(pixbuf, @area.x, @area.y,
|
40
|
-
@area.width, @area.height)
|
41
|
-
else
|
42
|
-
pixbuf
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
class Rotate < ImageOp
|
47
|
-
attr_accessor :angle
|
48
|
-
def initialize(angle=0)
|
49
|
-
super()
|
50
|
-
@angle = angle
|
51
|
-
end
|
52
|
-
def call(image, pixbuf)
|
53
|
-
if @angle.zero?
|
54
|
-
pixbuf
|
55
|
-
else
|
56
|
-
case @angle
|
57
|
-
when 0, 90, 180, 270
|
58
|
-
PixbufUtils::rotate(pixbuf, @angle)
|
59
|
-
else
|
60
|
-
raise "Not a valid angle"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
class Straighten < ImageOp
|
66
|
-
attr_accessor :angle
|
67
|
-
def initialize(angle=0)
|
68
|
-
super()
|
69
|
-
@angle = angle
|
70
|
-
end
|
71
|
-
def call(image, pixbuf)
|
72
|
-
if @angle.zero?
|
73
|
-
pixbuf
|
74
|
-
else
|
75
|
-
surface = Cairo::ImageSurface.new(:rgb24, pixbuf.width, pixbuf.height)
|
76
|
-
|
77
|
-
rotationValueRad = @angle * (Math::PI/180)
|
78
|
-
|
79
|
-
ratio = pixbuf.width.to_f/pixbuf.height
|
80
|
-
rh = (pixbuf.height) / ((ratio * Math.sin(rotationValueRad.abs)) + Math.cos(rotationValueRad.abs))
|
81
|
-
scale = pixbuf.height / rh.to_f.abs
|
82
|
-
|
83
|
-
a_ratio = pixbuf.height.to_f/pixbuf.width
|
84
|
-
a_rh = (pixbuf.width) / ((a_ratio * Math.sin(rotationValueRad.abs)) + Math.cos(rotationValueRad.abs))
|
85
|
-
a_scale = pixbuf.width / a_rh.to_f.abs
|
86
|
-
|
87
|
-
scale = a_scale if a_scale > scale
|
88
|
-
|
89
|
-
cr = Cairo::Context.new(surface)
|
90
|
-
#p [@angle, rotationValueRad, rh, scale, pixbuf.height]
|
91
|
-
|
92
|
-
cr.translate(pixbuf.width / 2.0, pixbuf.height / 2.0)
|
93
|
-
cr.rotate(rotationValueRad)
|
94
|
-
cr.scale(scale, scale)
|
95
|
-
cr.translate(pixbuf.width / -2.0, pixbuf.height / - 2.0)
|
96
|
-
cr.set_source_pixbuf(pixbuf)
|
97
|
-
|
98
|
-
cr.rectangle(0, 0, pixbuf.width, pixbuf.height)
|
99
|
-
cr.paint(1.0)
|
100
|
-
final_pb = surface.to_pixbuf
|
101
|
-
cr.destroy
|
102
|
-
surface.destroy
|
103
|
-
return final_pb
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
class ImageCaption < ImageOp
|
109
|
-
attr_accessor :text, :font, :position
|
110
|
-
def initialize()
|
111
|
-
super()
|
112
|
-
end
|
113
|
-
|
114
|
-
def font
|
115
|
-
@font || "Open Sans Condensed Light #{ ([@pixbuf.width,@pixbuf.height].max/80.0).to_i }"
|
116
|
-
end
|
117
|
-
|
118
|
-
def position
|
119
|
-
@position ||
|
120
|
-
(@pixbuf ? ([ [@pixbuf.width,@pixbuf.height].max/20 ] * 2) : [100, 100])
|
121
|
-
end
|
122
|
-
|
123
|
-
def call(image, pixbuf)
|
124
|
-
@pixbuf = pixbuf
|
125
|
-
surface = Cairo::ImageSurface.new(:rgb24, pixbuf.width, pixbuf.height)
|
126
|
-
cr = Cairo::Context.new(surface)
|
127
|
-
|
128
|
-
cr.save do
|
129
|
-
cr.set_source_pixbuf(pixbuf)
|
130
|
-
#cr.rectangle(0, 0, pixbuf.width, pixbuf.height)
|
131
|
-
cr.paint(1.0)
|
132
|
-
cr.translate(*self.position)
|
133
|
-
|
134
|
-
layout = cr.create_pango_layout
|
135
|
-
layout.set_text(self.text)
|
136
|
-
fd = Pango::FontDescription.new(self.font)
|
137
|
-
layout.font_description = fd
|
138
|
-
layout.set_width((pixbuf.width - self.position[0] - 100)*Pango::SCALE)
|
139
|
-
layout.context_changed
|
140
|
-
ink, _ = layout.pixel_extents
|
141
|
-
cr.set_source_rgba(0, 0, 0, 0.3)
|
142
|
-
cr.rectangle(-25, -25, ink.width + 50, ink.height + 50)
|
143
|
-
cr.fill
|
144
|
-
cr.set_source_rgb(1, 1, 1)
|
145
|
-
cr.show_pango_layout(layout)
|
146
|
-
end
|
147
|
-
|
148
|
-
|
149
|
-
final_pb = surface.to_pixbuf
|
150
|
-
cr.destroy
|
151
|
-
surface.destroy
|
152
|
-
return final_pb
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
class ImageBorder < ImageOp
|
157
|
-
attr_accessor :style, :colour, :crop, :size, :print_size, :shrink, :border_size
|
158
|
-
def initialize(style='none', colour='white')
|
159
|
-
super()
|
160
|
-
@style = style
|
161
|
-
@colour = colour
|
162
|
-
end
|
163
|
-
|
164
|
-
def call(image, pixbuf)
|
165
|
-
return pixbuf unless %w[square retro].include? @style
|
166
|
-
surface = Cairo::ImageSurface.new(:rgb24, pixbuf.width, pixbuf.height)
|
167
|
-
cr = Cairo::Context.new(surface)
|
168
|
-
|
169
|
-
img_width = pixbuf.width
|
170
|
-
img_height = pixbuf.height
|
171
|
-
|
172
|
-
cr.save do
|
173
|
-
if @crop
|
174
|
-
if @crop[0] < 0 || @crop[1] < 0
|
175
|
-
img_width = size[0]
|
176
|
-
img_height = size[1]
|
177
|
-
cr.translate( - @crop[0], - @crop[1])
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
cr.save do
|
182
|
-
cr.set_operator :source
|
183
|
-
cr.set_source_rgb 1, 1, 1
|
184
|
-
cr.paint
|
185
|
-
|
186
|
-
cr.rectangle(0, 0, img_width, img_height)
|
187
|
-
case colour
|
188
|
-
when 'dominant'
|
189
|
-
pixbuf.scale_max(400).save(fn="/tmp/hist-#{$$}.#{Time.now.to_i}", 'jpeg')
|
190
|
-
hgram = Colorscore::Histogram.new(fn)
|
191
|
-
File.unlink(fn) rescue nil
|
192
|
-
col = hgram.scores.first[1]
|
193
|
-
cr.set_source_rgb col.red/256.0, col.green/256.0, col.blue/256.0
|
194
|
-
when 'retro'
|
195
|
-
cr.set_source_rgb 1, 1, 0.8
|
196
|
-
when 'black'
|
197
|
-
cr.set_source_rgb 0, 0, 0
|
198
|
-
else
|
199
|
-
cr.set_source_rgb 1, 1, 1
|
200
|
-
end
|
201
|
-
cr.fill
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
border_scale = [img_width,img_height].max.to_f / print_size.max.to_i
|
206
|
-
size = @border_size
|
207
|
-
size *= border_scale
|
208
|
-
x, y = size, size
|
209
|
-
|
210
|
-
# This biggest impact will be on the smallest side, so to avoid white
|
211
|
-
# edges between photo and border scale by the longest changed side.
|
212
|
-
longest_side = [pixbuf.width, pixbuf.height].max.to_f
|
213
|
-
|
214
|
-
# Should be less than 1
|
215
|
-
pb_scale = (longest_side - (size * 2)) / longest_side
|
216
|
-
|
217
|
-
if @crop
|
218
|
-
if @crop[0] < 0 || @crop[1] < 0
|
219
|
-
x -= @crop[0]
|
220
|
-
y -= @crop[1]
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
case style
|
225
|
-
when 'retro'
|
226
|
-
# WARNING: CairoUtils class is not available in this gem!
|
227
|
-
CairoUtils.rounded_rectangle(cr, x, y,
|
228
|
-
img_width + x - (size*2), img_height+y-(size*2), size)
|
229
|
-
when 'square'
|
230
|
-
cr.rectangle(x, y, img_width - (size*2), img_height - (size*2))
|
231
|
-
end
|
232
|
-
cr.clip()
|
233
|
-
|
234
|
-
if @shrink
|
235
|
-
cr.translate(size, size)
|
236
|
-
cr.scale(pb_scale, pb_scale)
|
237
|
-
end
|
238
|
-
cr.set_source_pixbuf(pixbuf)
|
239
|
-
cr.rectangle(0, 0, pixbuf.width, pixbuf.height)
|
240
|
-
|
241
|
-
cr.paint(1.0)
|
242
|
-
final_pb = surface.to_pixbuf
|
243
|
-
cr.destroy
|
244
|
-
surface.destroy
|
245
|
-
return final_pb
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
class Gamma < ImageOp
|
250
|
-
attr_reader :gamma
|
251
|
-
def initialize(gamma=1.0)
|
252
|
-
super()
|
253
|
-
@gamma = gamma
|
254
|
-
end
|
255
|
-
def call(image, pixbuf)
|
256
|
-
if @gamma == 1.0
|
257
|
-
pixbuf
|
258
|
-
else
|
259
|
-
PixbufUtils.gamma(pixbuf, @gamma)
|
260
|
-
end
|
261
|
-
end
|
262
|
-
def priority
|
263
|
-
90
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
class Colourify < ImageOp
|
268
|
-
attr_reader :op
|
269
|
-
def initialize(op, alpha=255)
|
270
|
-
super()
|
271
|
-
@op = op
|
272
|
-
@alpha = alpha
|
273
|
-
end
|
274
|
-
|
275
|
-
def alpha
|
276
|
-
@alpha || 255
|
277
|
-
end
|
278
|
-
|
279
|
-
def sepia(pixbuf)
|
280
|
-
PixbufUtils.tint(pixbuf, 25, 5, -25, alpha)
|
281
|
-
end
|
282
|
-
|
283
|
-
def bluetone(pixbuf)
|
284
|
-
PixbufUtils.tint(pixbuf, -10, 5, 25, alpha)
|
285
|
-
end
|
286
|
-
|
287
|
-
def null(pixbuf)
|
288
|
-
pixbuf
|
289
|
-
end
|
290
|
-
alias :full :null # WebKiosk
|
291
|
-
alias :colour :null # WebKiosk
|
292
|
-
|
293
|
-
def greyscale(pixbuf)
|
294
|
-
PixbufUtils.tint(pixbuf, 0, 0, 0, alpha)
|
295
|
-
end
|
296
|
-
alias :bw :greyscale # WebKiosk
|
297
|
-
|
298
|
-
def call(image, pixbuf)
|
299
|
-
if @op and respond_to?(@op)
|
300
|
-
__send__(@op, pixbuf)
|
301
|
-
else
|
302
|
-
pixbuf # Default is nothing
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
end
|
data/lib/morandi/utils.rb
DELETED
@@ -1,136 +0,0 @@
|
|
1
|
-
require 'gdk_pixbuf2'
|
2
|
-
|
3
|
-
module Morandi
|
4
|
-
module Utils
|
5
|
-
module_function
|
6
|
-
def autocrop_coords(iw, ih, width, height)
|
7
|
-
return nil unless width
|
8
|
-
aspect = width.to_f / height.to_f
|
9
|
-
iaspect = iw.to_f / ih.to_f
|
10
|
-
|
11
|
-
if ih > iw
|
12
|
-
# Portrait image
|
13
|
-
# Check whether the aspect ratio is greater or smaller
|
14
|
-
# ie. where constraints will hit
|
15
|
-
aspect = height.to_f / width.to_f
|
16
|
-
end
|
17
|
-
|
18
|
-
# Landscape
|
19
|
-
if aspect > iaspect
|
20
|
-
# Width constraint - aspect-rect wider
|
21
|
-
crop_width = iw
|
22
|
-
crop_height = (crop_width / aspect).to_i
|
23
|
-
else
|
24
|
-
# Height constraint - aspect-rect wider
|
25
|
-
crop_height = ih
|
26
|
-
crop_width = (crop_height * aspect).to_i
|
27
|
-
end
|
28
|
-
|
29
|
-
[
|
30
|
-
((iw - crop_width)>>1),
|
31
|
-
((ih - crop_height)>>1),
|
32
|
-
crop_width,
|
33
|
-
crop_height
|
34
|
-
].map { |i| i.to_i }
|
35
|
-
end
|
36
|
-
|
37
|
-
def constrain(val,min,max)
|
38
|
-
if val < min
|
39
|
-
min
|
40
|
-
elsif val > max
|
41
|
-
max
|
42
|
-
else
|
43
|
-
val
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def apply_crop(pixbuf, x, y, w, h, fill_col = 0xffffffff)
|
48
|
-
if (x < 0) or (y < 0) || ((x+w) > pixbuf.width) || ((y+h) > pixbuf.height)
|
49
|
-
#tw, th = [w-x,w].max, [h-y,h].max
|
50
|
-
base_pixbuf = GdkPixbuf::Pixbuf.new(
|
51
|
-
colorspace: GdkPixbuf::Colorspace::RGB,
|
52
|
-
has_alpha: false,
|
53
|
-
bits_per_sample: 8,
|
54
|
-
width: w,
|
55
|
-
height: h
|
56
|
-
)
|
57
|
-
base_pixbuf.fill!(fill_col)
|
58
|
-
dest_x = [x, 0].min
|
59
|
-
dest_y = [y, 0].min
|
60
|
-
#src_x = [x,0].max
|
61
|
-
#src_y = [y,0].max
|
62
|
-
dest_x = [-x,0].max
|
63
|
-
dest_y = [-y,0].max
|
64
|
-
|
65
|
-
#if x < 0
|
66
|
-
#else
|
67
|
-
#end
|
68
|
-
#if y < 0
|
69
|
-
# dest_h = [h-dest_y, pixbuf.height, base_pixbuf.height-dest_y].min
|
70
|
-
#else
|
71
|
-
# dest_h = [h,pixbuf.height].min
|
72
|
-
#end
|
73
|
-
# dest_w = [w-dest_x, pixbuf.width, base_pixbuf.width-dest_x].min
|
74
|
-
|
75
|
-
offset_x = [x,0].max
|
76
|
-
offset_y = [y,0].max
|
77
|
-
copy_w = [w, pixbuf.width - offset_x].min
|
78
|
-
copy_h = [h, pixbuf.height - offset_y].min
|
79
|
-
|
80
|
-
paste_x = [x, 0].min * -1
|
81
|
-
paste_y = [y, 0].min * -1
|
82
|
-
|
83
|
-
if copy_w + paste_x > base_pixbuf.width
|
84
|
-
copy_w = base_pixbuf.width - paste_x
|
85
|
-
end
|
86
|
-
if copy_h + paste_y > base_pixbuf.height
|
87
|
-
copy_h = base_pixbuf.height - paste_y
|
88
|
-
end
|
89
|
-
base_pixbuf.composite! pixbuf, {
|
90
|
-
dest_x: paste_x,
|
91
|
-
dest_y: paste_y,
|
92
|
-
dest_width: copy_w,
|
93
|
-
dest_height: copy_h,
|
94
|
-
offset_x: paste_x - offset_x,
|
95
|
-
offset_y: paste_y - offset_y,
|
96
|
-
scale_x: 1,
|
97
|
-
scale_y: 1,
|
98
|
-
interpolation_type: :hyper,
|
99
|
-
overall_alpha: 255
|
100
|
-
}
|
101
|
-
pixbuf = base_pixbuf
|
102
|
-
else
|
103
|
-
x = constrain(x, 0, pixbuf.width)
|
104
|
-
y = constrain(y, 0, pixbuf.height)
|
105
|
-
w = constrain(w, 1, pixbuf.width - x)
|
106
|
-
h = constrain(h, 1, pixbuf.height - y)
|
107
|
-
#p [pixbuf, x, y, w, h]
|
108
|
-
pixbuf = pixbuf.subpixbuf(x, y, w, h)
|
109
|
-
end
|
110
|
-
pixbuf
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
class GdkPixbuf::Pixbuf
|
116
|
-
unless defined?(::GdkPixbuf::Pixbuf::InterpType)
|
117
|
-
InterpType = GdkPixbuf::InterpType
|
118
|
-
end
|
119
|
-
|
120
|
-
def scale_max(max_size, interp = GdkPixbuf::Pixbuf::InterpType::BILINEAR, max_scale = 1.0)
|
121
|
-
mul = (max_size / [width,height].max.to_f)
|
122
|
-
mul = [max_scale = 1.0,mul].min
|
123
|
-
scale(width * mul, height * mul, interp)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class Cairo::ImageSurface
|
128
|
-
def to_pixbuf
|
129
|
-
loader = GdkPixbuf::PixbufLoader.new
|
130
|
-
io = StringIO.new
|
131
|
-
write_to_png(io)
|
132
|
-
io.rewind
|
133
|
-
loader.last_write(io.read)
|
134
|
-
loader.pixbuf
|
135
|
-
end
|
136
|
-
end
|
data/morandi.gemspec
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'morandi/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "morandi"
|
8
|
-
spec.version = Morandi::VERSION
|
9
|
-
spec.authors = ["Geoff Youngs\n\n\n"]
|
10
|
-
spec.email = ["git@intersect-uk.co.uk"]
|
11
|
-
spec.summary = %q{Simple Image Edits}
|
12
|
-
spec.description = %q{Apply simple edits to images}
|
13
|
-
spec.homepage = ""
|
14
|
-
spec.license = "MIT"
|
15
|
-
|
16
|
-
spec.files = `git ls-files -z`.split("\x0")
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = ["lib"]
|
20
|
-
|
21
|
-
spec.add_dependency "gtk2"
|
22
|
-
spec.add_dependency "gdk_pixbuf2", "~> 3.4.0"
|
23
|
-
spec.add_dependency "cairo"
|
24
|
-
spec.add_dependency "pixbufutils"
|
25
|
-
spec.add_dependency "redeye"
|
26
|
-
spec.add_dependency "pango"
|
27
|
-
spec.add_dependency "colorscore"
|
28
|
-
|
29
|
-
spec.add_development_dependency "bundler"
|
30
|
-
spec.add_development_dependency "pry"
|
31
|
-
spec.add_development_dependency "rake"
|
32
|
-
spec.add_development_dependency "rspec"
|
33
|
-
end
|
data/sample/sample.jpg
DELETED
Binary file
|
data/spec/morandi_spec.rb
DELETED
@@ -1,208 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'fileutils'
|
4
|
-
require 'morandi'
|
5
|
-
|
6
|
-
RSpec.describe Morandi, '#process' do
|
7
|
-
context 'in command mode' do
|
8
|
-
it 'should create ouptut' do
|
9
|
-
Morandi.process('sample/sample.jpg', {}, out = 'sample/out_plain.jpg')
|
10
|
-
expect(File.exist?(out))
|
11
|
-
end
|
12
|
-
|
13
|
-
context "with a big image and a bigger cropped area to fill" do
|
14
|
-
it 'should create ouptut' do
|
15
|
-
settings = {
|
16
|
-
"crop"=>"0,477,15839,18804",
|
17
|
-
"angle"=>90,
|
18
|
-
"fx"=>"colour",
|
19
|
-
"straighten"=>0.0,
|
20
|
-
"gamma"=>0.98,
|
21
|
-
"redeye"=>[]
|
22
|
-
}
|
23
|
-
|
24
|
-
Morandi.process('sample/sample.jpg', settings, out = 'sample/out_crop_glitch_image.jpg')
|
25
|
-
expect(File.exist?(out))
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'should do rotation of images' do
|
30
|
-
original = GdkPixbuf::Pixbuf.get_file_info('sample/sample.jpg')
|
31
|
-
Morandi.process('sample/sample.jpg', {
|
32
|
-
'angle' => 90
|
33
|
-
}, out = 'sample/out_rotate90.jpg')
|
34
|
-
expect(File.exist?(out))
|
35
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
36
|
-
expect(original[1]).to eq(height)
|
37
|
-
expect(original[2]).to eq(width)
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'should accept pixbufs as an argument' do
|
41
|
-
pixbuf = GdkPixbuf::Pixbuf.new(file: 'sample/sample.jpg')
|
42
|
-
pro = Morandi::ImageProcessor.new(pixbuf, {}, {})
|
43
|
-
pro.process!
|
44
|
-
expect(pixbuf.width).to eq(pro.result.width)
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'should do cropping of images' do
|
48
|
-
Morandi.process('sample/sample.jpg', {
|
49
|
-
'crop' => [10, 10, 300, 300]
|
50
|
-
}, out = 'sample/out_crop.jpg')
|
51
|
-
expect(File.exist?(out))
|
52
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
53
|
-
expect(width).to eq(300)
|
54
|
-
expect(height).to eq(300)
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'should use user supplied path.icc' do
|
58
|
-
src = 'sample/sample.jpg'
|
59
|
-
icc = '/tmp/this-is-secure-thing.jpg'
|
60
|
-
default_icc = Morandi::ImageProcessor.default_icc_path(src)
|
61
|
-
out = 'sample/out_icc.jpg'
|
62
|
-
FileUtils.rm_f(default_icc)
|
63
|
-
Morandi.process(src, {}, out, 'path.icc' => icc)
|
64
|
-
expect(File).to exist(icc)
|
65
|
-
expect(File).not_to exist(default_icc)
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'should ignore user supplied path.icc' do
|
69
|
-
src = 'sample/sample.jpg'
|
70
|
-
icc = '/tmp/this-is-insecure-thing.jpg'
|
71
|
-
default_icc = Morandi::ImageProcessor.default_icc_path(src)
|
72
|
-
FileUtils.rm_f(icc)
|
73
|
-
FileUtils.rm_f(default_icc)
|
74
|
-
out = 'sample/out_icc.jpg'
|
75
|
-
Morandi.process(src, { 'path.icc' => icc, 'output.max' => 200 }, out)
|
76
|
-
expect(File).not_to exist(icc)
|
77
|
-
expect(File).to exist(default_icc)
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'should do cropping of images with a string' do
|
81
|
-
Morandi.process('sample/sample.jpg', {
|
82
|
-
'crop' => '10,10,300,300'
|
83
|
-
}, out = 'sample/out_crop.jpg')
|
84
|
-
expect(File.exist?(out))
|
85
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
86
|
-
expect(width).to eq(300)
|
87
|
-
expect(height).to eq(300)
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'should reduce the size of images' do
|
91
|
-
Morandi.process('sample/sample.jpg', {
|
92
|
-
'output.max' => 200
|
93
|
-
}, out = 'sample/out_reduce.jpg')
|
94
|
-
expect(File.exist?(out))
|
95
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
96
|
-
expect(width).to be <= 200
|
97
|
-
expect(height).to be <= 200
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'should reduce the straighten images' do
|
101
|
-
Morandi.process('sample/sample.jpg', {
|
102
|
-
'straighten' => 5
|
103
|
-
}, out = 'sample/out_straighten.jpg')
|
104
|
-
expect(File.exist?(out))
|
105
|
-
info, _, _ = GdkPixbuf::Pixbuf.get_file_info(out)
|
106
|
-
expect(info.name).to eq('jpeg')
|
107
|
-
end
|
108
|
-
|
109
|
-
it 'should reduce the gamma correct images' do
|
110
|
-
Morandi.process('sample/sample.jpg', {
|
111
|
-
'gamma' => 1.2
|
112
|
-
}, out = 'sample/out_gamma.jpg')
|
113
|
-
expect(File.exist?(out))
|
114
|
-
info, _, _ = GdkPixbuf::Pixbuf.get_file_info(out)
|
115
|
-
expect(info.name).to eq('jpeg')
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'should reduce the size of images' do
|
119
|
-
Morandi.process('sample/sample.jpg', {
|
120
|
-
'fx' => 'sepia'
|
121
|
-
}, out = 'sample/out_sepia.jpg')
|
122
|
-
expect(File.exist?(out))
|
123
|
-
info, _, _ = GdkPixbuf::Pixbuf.get_file_info(out)
|
124
|
-
expect(info.name).to eq('jpeg')
|
125
|
-
end
|
126
|
-
|
127
|
-
it 'should output at the specified size' do
|
128
|
-
Morandi.process('sample/sample.jpg', {
|
129
|
-
'output.width' => 300,
|
130
|
-
'output.height' => 200,
|
131
|
-
'image.auto-crop' => true,
|
132
|
-
'output.limit' => true
|
133
|
-
}, out = 'sample/out_at_size.jpg')
|
134
|
-
expect(File.exist?(out))
|
135
|
-
info, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
136
|
-
expect(info.name).to eq('jpeg')
|
137
|
-
expect(width).to be <= 300
|
138
|
-
expect(height).to be <= 200
|
139
|
-
end
|
140
|
-
|
141
|
-
it 'should blur the image' do
|
142
|
-
Morandi.process('sample/sample.jpg', {
|
143
|
-
'sharpen' => -3
|
144
|
-
}, out = 'sample/out_blur.jpg')
|
145
|
-
expect(File.exist?(out))
|
146
|
-
end
|
147
|
-
|
148
|
-
it 'should apply a border and maintain the target size' do
|
149
|
-
Morandi.process('sample/sample.jpg', {
|
150
|
-
'border-style' => 'square',
|
151
|
-
'background-style' => 'dominant',
|
152
|
-
'border-size-mm' => 5,
|
153
|
-
'output.width' => 800,
|
154
|
-
'output.height' => 650
|
155
|
-
}, out = 'sample/out_border.jpg')
|
156
|
-
expect(File.exist?(out))
|
157
|
-
|
158
|
-
info, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
159
|
-
expect(info.name).to eq('jpeg')
|
160
|
-
expect(width).to eq 800
|
161
|
-
expect(height).to eq 650
|
162
|
-
end
|
163
|
-
|
164
|
-
it 'should apply multiple transformations' do
|
165
|
-
Morandi.process('sample/sample.jpg', {
|
166
|
-
'brighten' => 5,
|
167
|
-
'contrast' => 5,
|
168
|
-
'sharpen' => 2,
|
169
|
-
'fx' => 'greyscale',
|
170
|
-
'border-style' => 'solid',
|
171
|
-
'background-style' => '#00FF00',
|
172
|
-
'crop' => [50, 0, 750, 650],
|
173
|
-
'output.width' => 300,
|
174
|
-
'output.height' => 260,
|
175
|
-
'output.limit' => true
|
176
|
-
}, out = 'sample/out_various.jpg')
|
177
|
-
expect(File.exist?(out))
|
178
|
-
|
179
|
-
info, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
180
|
-
expect(info.name).to eq('jpeg')
|
181
|
-
expect(width).to eq 300
|
182
|
-
expect(height).to eq 260
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
context 'with increasing quality settings' do
|
187
|
-
let(:max_quality_file_size) do
|
188
|
-
Morandi.process('sample/sample.jpg', { 'quality' => 100 }, 'sample/out-100.jpg')
|
189
|
-
File.size('sample/out-100.jpg')
|
190
|
-
end
|
191
|
-
|
192
|
-
let(:default_of_97_quality) do
|
193
|
-
Morandi.process('sample/sample.jpg', {}, 'sample/out-97.jpg')
|
194
|
-
File.size('sample/out-97.jpg')
|
195
|
-
end
|
196
|
-
|
197
|
-
let(:quality_of_40_by_options_args) do
|
198
|
-
Morandi.process('sample/sample.jpg', { 'quality' => 40 }, 'sample/out-40.jpg')
|
199
|
-
File.size('sample/out-40.jpg')
|
200
|
-
end
|
201
|
-
|
202
|
-
# Sort the output files' sizes and expect them to match to quality order
|
203
|
-
it 'creates files of increasing size' do
|
204
|
-
created_file_sizes = [default_of_97_quality, max_quality_file_size, quality_of_40_by_options_args].sort
|
205
|
-
expect(created_file_sizes).to eq([quality_of_40_by_options_args, default_of_97_quality, max_quality_file_size])
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
-
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
-
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
-
# loaded once.
|
5
|
-
#
|
6
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
-
|
8
|
-
require "pry"
|
9
|
-
|
10
|
-
RSpec.configure do |config|
|
11
|
-
config.run_all_when_everything_filtered = true
|
12
|
-
config.filter_run :focus
|
13
|
-
|
14
|
-
# Run specs in random order to surface order dependencies. If you find an
|
15
|
-
# order dependency and want to debug it, you can fix the order by providing
|
16
|
-
# the seed, which is printed after each run.
|
17
|
-
# --seed 1234
|
18
|
-
config.order = 'random'
|
19
|
-
end
|