morandi 0.12.1 → 0.99.4
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 +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
|