morandi 0.9.3 → 0.10.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 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