morandi 0.9.3 → 0.10.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
  SHA1:
3
- metadata.gz: 5b446e58f8982945add37c6917e14057b30215d0
4
- data.tar.gz: 856a29d43ed9b0a20e2749ecc05d35909b0fbdac
3
+ metadata.gz: 9709ee0d1808ab9b751fa10623c35081f774bb24
4
+ data.tar.gz: b10b4fadc2db8deb3e922b104f72245d6df1202a
5
5
  SHA512:
6
- metadata.gz: 7263157b0d52a46fc73e8d096401ee216a93c370c47d0e2a1bbaf8dd463c00df664a05bfc3872b09596142148fa4414bcb8b6747c3e7f86229db120b607d15c5
7
- data.tar.gz: 3af5276793f707bc7f4aa2e594eb1a810ad1bfc8fe8aa89aa96fe68a22e317a584fc9ffab5d492141b18e3b81185062c1a50bb516cda1a571089915d530f6b39
6
+ metadata.gz: f2b0bc4172387a86d735142205c98e3747f018f8a7ddf40e0a3277782669ab017ba3d7b5b8204d83edfbeeef92589771820960a527ae10005a839bfda7314916
7
+ data.tar.gz: a1b0dff226051a77efa68291f2e6d207505fff81878ab41180f35eca8e7df66fcd9631f3e05683751b943d0a26efbae9c4e0162b79e839cfe53f7c096478d85a
data/lib/morandi.rb CHANGED
@@ -5,260 +5,17 @@ require 'gdk_pixbuf2'
5
5
  require 'pixbufutils'
6
6
  require 'redeye'
7
7
 
8
+ require 'morandi/image_processor'
8
9
  require 'morandi/utils'
9
10
  require 'morandi/image-ops'
10
11
  require 'morandi/redeye'
11
12
 
12
13
  module Morandi
13
- # Your code goes here...
14
- class ImageProcessor
15
- attr_reader :options, :pb
16
- attr_accessor :config
17
-
18
- def valid_jpeg?(filename)
19
- return false unless File.exist?(filename)
20
- return false unless File.size(filename) > 0
21
-
22
- type, _, _ = Gdk::Pixbuf.get_file_info(filename)
23
-
24
- type.name == 'jpeg'
25
- rescue
26
- false
27
- end
28
- def self.default_icc_path(path)
29
- "#{path}.icc.jpg"
30
- end
31
-
32
- def initialize(file, user_options, local_options={})
33
- @file = file
34
-
35
- user_options.keys.grep(/^path/).each { |k| user_options.delete(k) }
36
-
37
- # Give priority to user_options
38
- @options = (local_options || {}).merge(user_options || {})
39
- @local_options = local_options
40
-
41
- @scale_to = @options['output.max']
42
- @width, @height = @options['output.width'], @options['output.height']
43
-
44
- load_file = @file
45
- type, width, height = Gdk::Pixbuf.get_file_info(load_file)
46
-
47
- if type.name.eql?('jpeg')
48
- icc_file = local_options['path.icc'] || ImageProcessor.default_icc_path(@file)
49
- if valid_jpeg?(icc_file) || system("jpgicc", "-q97", @file, icc_file)
50
- load_file = icc_file
51
- end
52
- end
53
-
54
- if @scale_to
55
- @pb = Gdk::Pixbuf.new(load_file, @scale_to, @scale_to)
56
- @src_max = [width, height].max
57
- @actual_max = [@pb.width, @pb.height].max
58
- else
59
- @pb = Gdk::Pixbuf.new(load_file)
60
- @src_max = [@pb.width, @pb.height].max
61
- @actual_max = [@pb.width, @pb.height].max
62
- end
63
-
64
- @scale = @actual_max / @src_max.to_f
65
- end
66
-
67
- def process!
68
- # Apply Red-Eye corrections
69
- apply_redeye!
70
-
71
- # Apply contrast, brightness etc
72
- apply_colour_manipulations!
73
-
74
- # apply rotation
75
- apply_rotate!
76
-
77
- # apply crop
78
- apply_crop!
79
-
80
- # apply filter
81
- apply_filters!
82
-
83
- # add border
84
- apply_decorations!
85
-
86
- if @options['output.limit'] && @width && @height
87
- @pb = @pb.scale_max([@width, @height].max)
88
- end
89
-
90
- @pb
91
- end
92
-
93
- SHARPEN = [
94
- -1, -1, -1, -1, -1,
95
- -1, 2, 2, 2, -1,
96
- -1, 2, 8, 2, -1,
97
- -1, 2, 2, 2, -1,
98
- -1, -1, -1, -1, -1,
99
- ]
100
- BLUR = [
101
- 0, 1, 1, 1, 0,
102
- 1, 1, 1, 1, 1,
103
- 1, 1, 1, 1, 1,
104
- 1, 1, 1, 1, 1,
105
- 0, 1, 1, 1, 0,
106
- ]
107
-
108
- def apply_colour_manipulations!
109
- #STDERR.puts "FILTER: #{options.inspect}"
110
- if options['brighten'].to_i.nonzero?
111
- brighten = [ [ 5 * options['brighten'], -100 ].max, 100 ].min
112
- #STDERR.puts([:brighten, brighten].inspect)
113
- @pb = PixbufUtils.brightness(@pb, brighten)
114
- end
115
- if options['gamma'] && (options['gamma'] != 1.0)
116
- @pb = PixbufUtils.gamma(@pb, options['gamma'])
117
- end
118
- if options['contrast'].to_i.nonzero?
119
- @pb = PixbufUtils.contrast(@pb, [ [ 5 * options['contrast'], -100 ].max, 100 ].min)
120
- end
121
- if options['sharpen'].to_i.nonzero?
122
- if options['sharpen'] > 0
123
- [options['sharpen'], 5].min.times do
124
- @pb = PixbufUtils.filter(@pb, SHARPEN, SHARPEN.inject(0, &:+))
125
- end
126
- elsif options['sharpen'] < 0
127
- [ (options['sharpen']*-1), 5].min.times do
128
- @pb = PixbufUtils.filter(@pb, BLUR, BLUR.inject(0, &:+))
129
- end
130
- end
131
- end
132
- end
133
-
134
- def apply_redeye!
135
- for eye in options['redeye'] || []
136
- @pb = Morandi::RedEye.tap_on(@pb, eye[0] * @scale, eye[1] * @scale)
137
- end
138
- end
139
-
140
- def angle
141
- a = options['angle'].to_i
142
- if a
143
- (360-a)%360
144
- else
145
- nil
146
- end
147
- end
148
-
149
- # modifies @pb with any applied rotation
150
- def apply_rotate!
151
- a = angle()
152
-
153
- unless (a%360).zero?
154
- @pb = @pb.rotate(a)
155
- end
156
-
157
- unless options['straighten'].to_f.zero?
158
- @pb = Morandi::Straighten.new(options['straighten'].to_f).call(nil, @pb)
159
- end
160
-
161
- @image_width = @pb.width
162
- @image_height = @pb.height
163
- end
164
-
165
- DEFAULT_CONFIG = {
166
- 'border-size-mm' => 5
167
- }
168
- def config_for(key)
169
- return options[key] if options && options.has_key?(key)
170
- return @config[key] if @config && @config.has_key?(key)
171
- DEFAULT_CONFIG[key]
172
- end
173
-
174
- #
175
- def apply_crop!
176
- crop = options['crop']
177
-
178
- if crop.nil? && config_for('image.auto-crop').eql?(false)
179
- return
180
- end
181
-
182
- if crop.is_a?(String) && crop =~ /^\d+,\d+,\d+,\d+/
183
- crop = crop.split(/,/).map(&:to_i)
184
- end
185
-
186
- crop = nil unless crop.is_a?(Array) && crop.size.eql?(4) && crop.all? { |i|
187
- i.kind_of?(Numeric)
188
- }
189
-
190
- # can't crop, won't crop
191
- return if @width.nil? && @height.nil? && crop.nil?
192
-
193
- if crop && @scale != 1.0
194
- crop = crop.map { |s| (s.to_f * @scale).floor }
195
- end
196
-
197
- crop ||= Morandi::Utils.autocrop_coords(@pb.width, @pb.height, @width, @height)
198
-
199
- @pb = Morandi::Utils.apply_crop(@pb, crop[0], crop[1], crop[2], crop[3])
200
- end
201
-
202
-
203
- def apply_filters!
204
- filter = options['fx']
205
-
206
- case filter
207
- when 'greyscale'
208
- op = Morandi::Colourify.new_from_hash('op' => filter)
209
- when 'sepia', 'bluetone'
210
- op = Morandi::Colourify.new_from_hash('op' => filter, 'alpha' => (0.85 * 255).to_i)
211
- else
212
- return
213
- end
214
- @pb = op.call(nil, @pb)
215
- end
216
-
217
- def apply_decorations!
218
- style, colour = options['border-style'], options['background-style']
219
-
220
- return if style.nil? or style.eql?('none')
221
- return if colour.eql?('none')
222
- colour ||= 'black'
223
-
224
- crop = options['crop']
225
- crop = crop.map { |s| (s.to_f * @scale).floor } if crop && @scale != 1.0
226
-
227
- op = Morandi::ImageBorder.new_from_hash(data={
228
- 'style' => style,
229
- 'colour' => colour || '#000000',
230
- 'crop' => crop,
231
- 'size' => [@image_width, @image_height],
232
- 'print_size' => [@width, @height],
233
- 'shrink' => true,
234
- 'border_size' => @scale * config_for('border-size-mm').to_i * 300 / 25.4 # 5mm at 300dpi
235
- })
236
-
237
- @pb = op.call(nil, @pb)
238
- end
239
-
240
- def write_to_png(fn, orientation=:any)
241
- pb = @pb
242
-
243
- case orientation
244
- when :landscape
245
- pb = @pb.rotate(90) if @pb.width < @pb.height
246
- when :portrait
247
- pb = @pb.rotate(90) if @pb.width > @pb.height
248
- end
249
- pb.save(fn, 'png')
250
- end
251
-
252
- def write_to_jpeg(fn, quality = 97)
253
- @pb.save(fn, 'jpeg', :quality => quality)
254
- end
255
- end
256
-
257
14
  module_function
258
15
  def process(file_in, options, out_file, local_options = {})
259
16
  pro = ImageProcessor.new(file_in, options, local_options)
260
17
  pro.process!
261
18
  pro.write_to_jpeg(out_file)
262
19
  end
263
-
264
20
  end
21
+
@@ -0,0 +1,242 @@
1
+ require 'morandi/profiled_pixbuf'
2
+
3
+ class Morandi::ImageProcessor
4
+ attr_reader :options, :pb
5
+ attr_accessor :config
6
+
7
+ def self.default_icc_path(path)
8
+ "#{path}.icc.jpg"
9
+ end
10
+
11
+ def initialize(file, user_options, local_options={})
12
+ @file = file
13
+
14
+ user_options.keys.grep(/^path/).each { |k| user_options.delete(k) }
15
+
16
+ # Give priority to user_options
17
+ @options = (local_options || {}).merge(user_options || {})
18
+ @local_options = local_options
19
+
20
+ @scale_to = @options['output.max']
21
+ @width, @height = @options['output.width'], @options['output.height']
22
+
23
+ if @file.is_a?(String)
24
+ get_pixbuf
25
+ elsif @file.is_a?(Gdk::Pixbuf) or @file.is_a?(Morandi::ProfiledPixbuf)
26
+ @pb = @file
27
+ end
28
+ end
29
+
30
+ def process!
31
+ # Apply Red-Eye corrections
32
+ apply_redeye!
33
+
34
+ # Apply contrast, brightness etc
35
+ apply_colour_manipulations!
36
+
37
+ # apply rotation
38
+ apply_rotate!
39
+
40
+ # apply crop
41
+ apply_crop!
42
+
43
+ # apply filter
44
+ apply_filters!
45
+
46
+ # add border
47
+ apply_decorations!
48
+
49
+ if @options['output.limit'] && @width && @height
50
+ @pb = @pb.scale_max([@width, @height].max)
51
+ end
52
+
53
+ @pb
54
+ end
55
+
56
+ def result
57
+ @pb
58
+ end
59
+
60
+ def write_to_png(fn, orientation=:any)
61
+ pb = @pb
62
+
63
+ case orientation
64
+ when :landscape
65
+ pb = @pb.rotate(90) if @pb.width < @pb.height
66
+ when :portrait
67
+ pb = @pb.rotate(90) if @pb.width > @pb.height
68
+ end
69
+ pb.save(fn, 'png')
70
+ end
71
+
72
+ def write_to_jpeg(fn, quality = 97)
73
+ @pb.save(fn, 'jpeg', :quality => quality)
74
+ end
75
+
76
+ protected
77
+ def get_pixbuf
78
+ _, width, height = Gdk::Pixbuf.get_file_info(@file)
79
+
80
+ if @scale_to
81
+ @pb = Morandi::ProfiledPixbuf.new(@file, @scale_to, @scale_to, @local_options)
82
+ @src_max = [width, height].max
83
+ @actual_max = [@pb.width, @pb.height].max
84
+ else
85
+ @pb = Morandi::ProfiledPixbuf.new(@file, @local_options)
86
+ @src_max = [@pb.width, @pb.height].max
87
+ @actual_max = [@pb.width, @pb.height].max
88
+ end
89
+
90
+ @scale = @actual_max / @src_max.to_f
91
+ end
92
+
93
+ SHARPEN = [
94
+ -1, -1, -1, -1, -1,
95
+ -1, 2, 2, 2, -1,
96
+ -1, 2, 8, 2, -1,
97
+ -1, 2, 2, 2, -1,
98
+ -1, -1, -1, -1, -1,
99
+ ]
100
+ BLUR = [
101
+ 0, 1, 1, 1, 0,
102
+ 1, 1, 1, 1, 1,
103
+ 1, 1, 1, 1, 1,
104
+ 1, 1, 1, 1, 1,
105
+ 0, 1, 1, 1, 0,
106
+ ]
107
+
108
+ def apply_colour_manipulations!
109
+ if options['brighten'].to_i.nonzero?
110
+ brighten = [ [ 5 * options['brighten'], -100 ].max, 100 ].min
111
+ @pb = PixbufUtils.brightness(@pb, brighten)
112
+ end
113
+
114
+ if options['gamma'] && (options['gamma'] != 1.0)
115
+ @pb = PixbufUtils.gamma(@pb, options['gamma'])
116
+ end
117
+
118
+ if options['contrast'].to_i.nonzero?
119
+ @pb = PixbufUtils.contrast(@pb, [ [ 5 * options['contrast'], -100 ].max, 100 ].min)
120
+ end
121
+
122
+ if options['sharpen'].to_i.nonzero?
123
+ if options['sharpen'] > 0
124
+ [options['sharpen'], 5].min.times do
125
+ @pb = PixbufUtils.filter(@pb, SHARPEN, SHARPEN.inject(0, &:+))
126
+ end
127
+ elsif options['sharpen'] < 0
128
+ [ (options['sharpen']*-1), 5].min.times do
129
+ @pb = PixbufUtils.filter(@pb, BLUR, BLUR.inject(0, &:+))
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ def apply_redeye!
136
+ for eye in options['redeye'] || []
137
+ @pb = Morandi::RedEye.tap_on(@pb, eye[0] * @scale, eye[1] * @scale)
138
+ end
139
+ end
140
+
141
+ def angle
142
+ a = options['angle'].to_i
143
+ if a
144
+ (360-a)%360
145
+ else
146
+ nil
147
+ end
148
+ end
149
+
150
+ # modifies @pb with any applied rotation
151
+ def apply_rotate!
152
+ a = angle()
153
+
154
+ unless (a%360).zero?
155
+ @pb = @pb.rotate(a)
156
+ end
157
+
158
+ unless options['straighten'].to_f.zero?
159
+ @pb = Morandi::Straighten.new(options['straighten'].to_f).call(nil, @pb)
160
+ end
161
+
162
+ @image_width = @pb.width
163
+ @image_height = @pb.height
164
+ end
165
+
166
+ DEFAULT_CONFIG = {
167
+ 'border-size-mm' => 5
168
+ }
169
+ def config_for(key)
170
+ return options[key] if options && options.has_key?(key)
171
+ return @config[key] if @config && @config.has_key?(key)
172
+ DEFAULT_CONFIG[key]
173
+ end
174
+
175
+ #
176
+ def apply_crop!
177
+ crop = options['crop']
178
+
179
+ if crop.nil? && config_for('image.auto-crop').eql?(false)
180
+ return
181
+ end
182
+
183
+ if crop.is_a?(String) && crop =~ /^\d+,\d+,\d+,\d+/
184
+ crop = crop.split(/,/).map(&:to_i)
185
+ end
186
+
187
+ crop = nil unless crop.is_a?(Array) && crop.size.eql?(4) && crop.all? { |i|
188
+ i.kind_of?(Numeric)
189
+ }
190
+
191
+ # can't crop, won't crop
192
+ return if @width.nil? && @height.nil? && crop.nil?
193
+
194
+ if crop && @scale != 1.0
195
+ crop = crop.map { |s| (s.to_f * @scale).floor }
196
+ end
197
+
198
+ crop ||= Morandi::Utils.autocrop_coords(@pb.width, @pb.height, @width, @height)
199
+
200
+ @pb = Morandi::Utils.apply_crop(@pb, crop[0], crop[1], crop[2], crop[3])
201
+ end
202
+
203
+
204
+ def apply_filters!
205
+ filter = options['fx']
206
+
207
+ case filter
208
+ when 'greyscale'
209
+ op = Morandi::Colourify.new_from_hash('op' => filter)
210
+ when 'sepia', 'bluetone'
211
+ op = Morandi::Colourify.new_from_hash('op' => filter, 'alpha' => (0.85 * 255).to_i)
212
+ else
213
+ return
214
+ end
215
+ @pb = op.call(nil, @pb)
216
+ end
217
+
218
+ def apply_decorations!
219
+ style, colour = options['border-style'], options['background-style']
220
+
221
+ return if style.nil? or style.eql?('none')
222
+ return if colour.eql?('none')
223
+ colour ||= 'black'
224
+
225
+ crop = options['crop']
226
+ crop = crop.map { |s| (s.to_f * @scale).floor } if crop && @scale != 1.0
227
+
228
+ op = Morandi::ImageBorder.new_from_hash(data={
229
+ 'style' => style,
230
+ 'colour' => colour || '#000000',
231
+ 'crop' => crop,
232
+ 'size' => [@image_width, @image_height],
233
+ 'print_size' => [@width, @height],
234
+ 'shrink' => true,
235
+ 'border_size' => @scale * config_for('border-size-mm').to_i * 300 / 25.4 # 5mm at 300dpi
236
+ })
237
+
238
+ @pb = op.call(nil, @pb)
239
+ end
240
+
241
+ end
242
+
@@ -0,0 +1,46 @@
1
+ require 'gdk_pixbuf2'
2
+
3
+ class Morandi::ProfiledPixbuf < Gdk::Pixbuf
4
+ def valid_jpeg?(filename)
5
+ return false unless File.exist?(filename)
6
+ return false unless File.size(filename) > 0
7
+
8
+ type, _, _ = Gdk::Pixbuf.get_file_info(filename)
9
+
10
+ type && type.name.eql?('jpeg')
11
+ rescue
12
+ false
13
+ end
14
+
15
+ def self.default_icc_path(path)
16
+ "#{path}.icc.jpg"
17
+ end
18
+
19
+ def initialize(*args)
20
+ @local_options = args.last.is_a?(Hash) && args.pop || {}
21
+
22
+ if args[0].is_a?(String)
23
+ @file = args[0]
24
+
25
+ if suitable_for_jpegicc?
26
+ icc_file = icc_cache_path
27
+
28
+ args[0] = icc_file if valid_jpeg?(icc_file) || system("jpgicc", "-q97", @file, icc_file)
29
+ end
30
+ end
31
+
32
+ super(*args)
33
+ end
34
+
35
+
36
+ protected
37
+ def suitable_for_jpegicc?
38
+ type, _, _ = Gdk::Pixbuf.get_file_info(@file)
39
+
40
+ type && type.name.eql?('jpeg')
41
+ end
42
+
43
+ def icc_cache_path
44
+ @local_options['path.icc'] || Morandi::ProfiledPixbuf.default_icc_path(@file)
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module Morandi
2
- VERSION = "0.9.3"
2
+ VERSION = "0.10.0"
3
3
  end
data/spec/morandi_spec.rb CHANGED
@@ -18,6 +18,13 @@ RSpec.describe Morandi, "#process_to_file" do
18
18
  expect(original[2]).to eq(w)
19
19
  end
20
20
 
21
+ it "should accept pixbufs as an argument" do
22
+ pixbuf = Gdk::Pixbuf.new("sample/sample.jpg")
23
+ pro = Morandi::ImageProcessor.new(pixbuf, {}, {})
24
+ pro.process!
25
+ expect(pixbuf.width).to eq(pro.result.width)
26
+ end
27
+
21
28
  it "should do cropping of images" do
22
29
  Morandi.process("sample/sample.jpg", {
23
30
  'crop' => [10,10,300,300]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morandi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - |+
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-07-18 00:00:00.000000000 Z
14
+ date: 2015-12-08 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: gtk2
@@ -168,6 +168,8 @@ files:
168
168
  - Rakefile
169
169
  - lib/morandi.rb
170
170
  - lib/morandi/image-ops.rb
171
+ - lib/morandi/image_processor.rb
172
+ - lib/morandi/profiled_pixbuf.rb
171
173
  - lib/morandi/redeye.rb
172
174
  - lib/morandi/utils.rb
173
175
  - lib/morandi/version.rb