morandi 0.12.1 → 0.99.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,82 +1,29 @@
1
- require 'gdk_pixbuf2'
2
-
3
- class Morandi::ProfiledPixbuf < GdkPixbuf::Pixbuf
4
- def valid_jpeg?(filename)
5
- return false unless File.exist?(filename)
6
- return false unless File.size(filename) > 0
7
-
8
- type, _, _ = GdkPixbuf::Pixbuf.get_file_info(filename)
9
-
10
- type && type.name.eql?('jpeg')
11
- rescue
12
- false
13
- end
14
-
15
- def self.from_string(string, loader: nil, chunk_size: 4096)
16
- loader ||= GdkPixbuf::PixbufLoader.new
17
- ((string.bytesize + chunk_size - 1) / chunk_size).times do |i|
18
- loader.write(string.byteslice(i * chunk_size, chunk_size))
19
- end
20
- loader.close
21
- loader.pixbuf
22
- end
23
-
24
- def self.default_icc_path(path)
25
- "#{path}.icc.jpg"
26
- end
27
-
28
- def initialize(*args)
29
- @local_options = args.last.is_a?(Hash) && args.pop || {}
1
+ # frozen_string_literal: true
30
2
 
31
- if args[0].is_a?(String)
32
- @file = args[0]
33
-
34
- if suitable_for_jpegicc?
35
- icc_file = icc_cache_path
3
+ require 'gdk_pixbuf2'
4
+ require 'morandi/srgb_conversion'
36
5
 
37
- args[0] = icc_file if valid_jpeg?(icc_file) || system("jpgicc", "-q97", @file, icc_file)
38
- end
39
- end
6
+ module Morandi
7
+ # ProfiledPixbuf is a descendent of GdkPixbuf::Pixbuf with ICC support.
8
+ # It attempts to load an image using jpegicc/littlecms to ensure that it is sRGB.
9
+ # NOTE: pixbuf supports colour profiles, but it requires an explicit icc-profile option to embed it when saving file
10
+ class ProfiledPixbuf < GdkPixbuf::Pixbuf
11
+ def initialize(path, local_options, max_size_px = nil)
12
+ @local_options = local_options
40
13
 
41
- # TODO: This is to fix some deprecation warnings. This needs refactoring.
42
- # All can be implemented without having to hack on the PixBuff gem.
43
- case args.size
44
- when 1
45
- super(file: args.first)
46
- when 3
47
- super(path: args[0], width: args[1], height: args[2])
48
- else
49
- super(*args)
50
- end
51
- rescue GdkPixbuf::PixbufError::CorruptImage => e
52
- if args[0].is_a?(String) && defined? Tempfile
53
- temp = Tempfile.new
54
- pixbuf = self.class.from_string(File.read(args[0]))
55
- pixbuf.save(temp.path, 'jpeg')
56
- args[0] = temp.path
14
+ path = srgb_path(path) || path
57
15
 
58
- if args.size == 1
59
- super file: args.first
16
+ if max_size_px
17
+ super(file: path, width: max_size_px, height: max_size_px)
60
18
  else
61
- super(*args)
19
+ super(file: path)
62
20
  end
63
-
64
- temp.close
65
- temp.unlink
66
- else
67
- throw e
68
21
  end
69
- end
70
-
71
22
 
72
- protected
73
- def suitable_for_jpegicc?
74
- type, _, _ = GdkPixbuf::Pixbuf.get_file_info(@file)
23
+ private
75
24
 
76
- type && type.name.eql?('jpeg')
77
- end
78
-
79
- def icc_cache_path
80
- @local_options['path.icc'] || Morandi::ProfiledPixbuf.default_icc_path(@file)
25
+ def srgb_path(original_path)
26
+ Morandi::SrgbConversion.perform(original_path, target_path: @local_options['path.icc'])
27
+ end
81
28
  end
82
29
  end
@@ -1,4 +1,4 @@
1
- require 'redeye'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Morandi
4
4
  module RedEye
@@ -6,57 +6,45 @@ module Morandi
6
6
  # The reason for its existence is to prevent the situations when the bigger red area causes an excessive correction
7
7
  # e.g. continuous red eyeglasses frame or sunburnt person's skin around eyes forming an area
8
8
  RED_AREA_DENSITY_THRESHOLD = 0.3
9
- end
10
- end
11
9
 
12
- module Morandi
13
- module RedEye
14
- module TapRedEye
15
- module_function
16
- def tap_on(pb, x, y)
17
- n = ([pb.height,pb.width].max / 10)
18
- x1 = [x - n, 0].max
19
- x2 = [x + n, pb.width].min
20
- y1 = [y - n, 0].max
21
- y2 = [y + n, pb.height].min
22
- return pb unless (x1 >= 0) && (x2 > x1) && (y1 >= 0) && (y2 > y1)
23
- redeye = ::RedEye.new(pb, x1, y1, x2, y2)
24
-
25
- sensitivity = 2
26
- blobs = redeye.identify_blobs(sensitivity).reject { |region|
27
- region.noPixels < 4 || !region.squareish?(0.5, RED_AREA_DENSITY_THRESHOLD)
28
- }.sort_by { |region|
29
- region.area_min_x = x1
30
- region.area_min_y = y1
31
-
32
- # Higher is better
33
- score = (region.noPixels) / (region.distance_from(x, y) ** 2)
34
- }
35
-
36
- blob = blobs.last
37
- redeye.correct_blob(blob.id) if blob
38
- pb = redeye.pixbuf
39
- end
40
- end
41
- end
42
- end
10
+ # RedEye finder that looks for "eye" closest to a point
11
+ module TapRedEye
12
+ module_function
43
13
 
44
- class ::RedEye::Region
45
- attr_accessor :area_min_x
46
- attr_accessor :area_min_y
47
- def centre
48
- [@area_min_x.to_i + ((maxX + minX) >> 1),
49
- @area_min_y.to_i + ((maxY + minY) >> 1)]
50
- end
14
+ def tap_on(pixbuf, x_coord, y_coord)
15
+ n = ([pixbuf.height, pixbuf.width].max / 10)
16
+ x1 = [x_coord - n, 0].max
17
+ x2 = [x_coord + n, pixbuf.width].min
18
+ y1 = [y_coord - n, 0].max
19
+ y2 = [y_coord + n, pixbuf.height].min
20
+
21
+ return pixbuf unless (x1 >= 0) && (x2 > x1) && (y1 >= 0) && (y2 > y1)
51
22
 
52
- # Pythagorean
53
- def distance_from(x,y)
54
- cx,cy = centre()
23
+ red_eye = MorandiNative::RedEye.new(pixbuf, x1, y1, x2, y2)
55
24
 
56
- dx = cx - x
57
- dy = cy - y
25
+ sensitivity = 2
26
+ blobs = red_eye.identify_blobs(sensitivity).reject do |region|
27
+ region.noPixels < 4 || !region.squareish?(0.5, RED_AREA_DENSITY_THRESHOLD)
28
+ end
58
29
 
59
- Math.sqrt( (dx * dx) + (dy * dy) )
30
+ sorted_blobs = blobs.sort_by do |region|
31
+ region.area_min_x = x1
32
+ region.area_min_y = y1
33
+ end
34
+
35
+ blob = sorted_blobs.last
36
+ red_eye.correct_blob(blob.id) if blob
37
+ red_eye.pixbuf
38
+ end
39
+ end
60
40
  end
61
41
  end
62
42
 
43
+ module MorandiNative
44
+ class RedEye
45
+ # Represents an area with a suspected red eye
46
+ class Region
47
+ attr_accessor :area_min_x, :area_min_y
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gdk_pixbuf2'
4
+
5
+ module Morandi
6
+ # Converts the file under `path` to sRGB colour space
7
+ class SrgbConversion
8
+ # Performs a conversion to srgb colour space if possible
9
+ # Returns a path to converted file on success or nil on failure
10
+ def self.perform(path, target_path: nil)
11
+ return unless suitable_for_jpegicc?(path)
12
+
13
+ icc_file_path = target_path || default_icc_path(path)
14
+ return icc_file_path if valid_jpeg?(icc_file_path)
15
+
16
+ system('jpgicc', '-q97', path, icc_file_path, out: '/dev/null', err: '/dev/null')
17
+
18
+ return unless valid_jpeg?(icc_file_path)
19
+
20
+ icc_file_path
21
+ end
22
+
23
+ def self.default_icc_path(path)
24
+ "#{path}.icc.jpg"
25
+ end
26
+
27
+ def self.valid_jpeg?(path)
28
+ return false unless File.exist?(path)
29
+ return false unless File.size(path).positive?
30
+
31
+ type, = GdkPixbuf::Pixbuf.get_file_info(path)
32
+
33
+ type && type.name.eql?('jpeg')
34
+ rescue StandardError
35
+ false
36
+ end
37
+
38
+ def self.suitable_for_jpegicc?(path)
39
+ valid_jpeg?(path)
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Morandi
2
- VERSION = '0.12.1'.freeze
4
+ VERSION = '0.99.4'
3
5
  end
data/lib/morandi.rb CHANGED
@@ -1,20 +1,50 @@
1
- require "morandi/version"
2
- require 'gtk2/base'
3
- require 'cairo'
4
- require 'gdk_pixbuf2'
5
- require 'pixbufutils'
6
- require 'redeye'
1
+ # frozen_string_literal: true
7
2
 
3
+ require 'morandi/version'
4
+ require 'morandi_native'
5
+
6
+ require 'morandi/cairo_ext'
7
+ require 'morandi/pixbuf_ext'
8
+ require 'morandi/errors'
8
9
  require 'morandi/image_processor'
9
- require 'morandi/utils'
10
- require 'morandi/image-ops'
11
10
  require 'morandi/redeye'
11
+ require 'morandi/crop_utils'
12
12
 
13
+ # Morandi namespace should contain all the functionality of the gem
13
14
  module Morandi
14
15
  module_function
15
- def process(file_in, options, out_file, local_options = {})
16
- pro = ImageProcessor.new(file_in, options, local_options)
17
- pro.process!
18
- pro.write_to_jpeg(out_file)
16
+
17
+ # The main entry point for the library
18
+ #
19
+ # @param source [String|GdkPixbuf::Pixbuf] source image
20
+ # @param [Hash] options The options describing expected processing to perform
21
+ # @option options [Integer] 'brighten' Change image brightness (-20..20)
22
+ # @option options [Float] 'gamma' Gamma correct image
23
+ # @option options [Integer] 'contrast' Change image contrast (-20..20)
24
+ # @option options [Integer] 'sharpen' Sharpen (1..5) / Blur (-1..-5)
25
+ # @option options [Array[[Integer,Integer],...]] 'redeye' Apply redeye correction at point
26
+ # @option options [Integer] 'angle' Rotate image clockwise by multiple of 90 (0, 90, 180, 270)
27
+ # @option options [Array[Integer,Integer,Integer,Integer]] 'crop' Crop image (x, y, width, height)
28
+ # @option options [String] 'fx' Apply colour filters ('greyscale', 'sepia', 'bluetone')
29
+ # @option options [String] 'border-style' Set border style ('square', 'retro')
30
+ # @option options [String] 'background-style' Set border colour ('retro', 'black', 'white', 'dominant')
31
+ # @option options [Integer] 'quality' (97) Set JPG compression value (1 to 100)
32
+ # @option options [Integer] 'output.max' Downscales the image to fit within the square of given size before
33
+ # processing to limit the required resources
34
+ # @option options [Integer] 'output.width' Sets desired width of resulting image
35
+ # @option options [Integer] 'output.height' Sets desired height of resulting image
36
+ # @option options [TrueClass|FalseClass] 'image.auto-crop' (true) If the output dimensions are set and this is true,
37
+ # image is cropped automatically to the desired
38
+ # dimensions.
39
+ # @option options [TrueClass|FalseClass] 'output.limit' (false) If the output dimensions are defined and this is true,
40
+ # the output image is scaled down to fit within square of
41
+ # size of the longer edge (ignoring shorter dimension!)
42
+ # @param target_path [String] target location for image
43
+ # @param local_options [Hash] Hash of options other than desired transformations
44
+ # @option local_options [String] 'path.icc' A path to store the input after converting to sRGB colour space
45
+ def process(source, options, target_path, local_options = {})
46
+ pro = ImageProcessor.new(source, options, local_options)
47
+ pro.result
48
+ pro.write_to_jpeg(target_path)
19
49
  end
20
50
  end
Binary file
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.12.1
4
+ version: 0.99.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - |+
@@ -11,192 +11,135 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2020-12-10 00:00:00.000000000 Z
14
+ date: 2024-11-22 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
- name: gtk2
18
- requirement: !ruby/object:Gem::Requirement
19
- requirements:
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: '0'
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: '0'
30
- - !ruby/object:Gem::Dependency
31
- name: gdk_pixbuf2
17
+ name: atk
32
18
  requirement: !ruby/object:Gem::Requirement
33
19
  requirements:
34
20
  - - "~>"
35
21
  - !ruby/object:Gem::Version
36
- version: 3.4.0
22
+ version: '4.0'
37
23
  type: :runtime
38
24
  prerelease: false
39
25
  version_requirements: !ruby/object:Gem::Requirement
40
26
  requirements:
41
27
  - - "~>"
42
28
  - !ruby/object:Gem::Version
43
- version: 3.4.0
29
+ version: '4.0'
44
30
  - !ruby/object:Gem::Dependency
45
31
  name: cairo
46
32
  requirement: !ruby/object:Gem::Requirement
47
33
  requirements:
48
- - - ">="
34
+ - - "~>"
49
35
  - !ruby/object:Gem::Version
50
- version: '0'
36
+ version: '1.0'
51
37
  type: :runtime
52
38
  prerelease: false
53
39
  version_requirements: !ruby/object:Gem::Requirement
54
40
  requirements:
55
- - - ">="
41
+ - - "~>"
56
42
  - !ruby/object:Gem::Version
57
- version: '0'
43
+ version: '1.0'
58
44
  - !ruby/object:Gem::Dependency
59
- name: pixbufutils
45
+ name: colorscore
60
46
  requirement: !ruby/object:Gem::Requirement
61
47
  requirements:
62
- - - ">="
48
+ - - "~>"
63
49
  - !ruby/object:Gem::Version
64
- version: '0'
50
+ version: '0.0'
65
51
  type: :runtime
66
52
  prerelease: false
67
53
  version_requirements: !ruby/object:Gem::Requirement
68
54
  requirements:
69
- - - ">="
55
+ - - "~>"
70
56
  - !ruby/object:Gem::Version
71
- version: '0'
57
+ version: '0.0'
72
58
  - !ruby/object:Gem::Dependency
73
- name: redeye
59
+ name: gdk_pixbuf2
74
60
  requirement: !ruby/object:Gem::Requirement
75
61
  requirements:
76
- - - ">="
62
+ - - "~>"
77
63
  - !ruby/object:Gem::Version
78
- version: '0'
64
+ version: '4.0'
79
65
  type: :runtime
80
66
  prerelease: false
81
67
  version_requirements: !ruby/object:Gem::Requirement
82
68
  requirements:
83
- - - ">="
69
+ - - "~>"
84
70
  - !ruby/object:Gem::Version
85
- version: '0'
71
+ version: '4.0'
86
72
  - !ruby/object:Gem::Dependency
87
73
  name: pango
88
74
  requirement: !ruby/object:Gem::Requirement
89
75
  requirements:
90
- - - ">="
76
+ - - "~>"
91
77
  - !ruby/object:Gem::Version
92
- version: '0'
78
+ version: '4.0'
93
79
  type: :runtime
94
80
  prerelease: false
95
81
  version_requirements: !ruby/object:Gem::Requirement
96
82
  requirements:
97
- - - ">="
83
+ - - "~>"
98
84
  - !ruby/object:Gem::Version
99
- version: '0'
85
+ version: '4.0'
100
86
  - !ruby/object:Gem::Dependency
101
- name: colorscore
87
+ name: rake-compiler
102
88
  requirement: !ruby/object:Gem::Requirement
103
89
  requirements:
104
- - - ">="
90
+ - - "~>"
105
91
  - !ruby/object:Gem::Version
106
- version: '0'
92
+ version: '1.2'
107
93
  type: :runtime
108
94
  prerelease: false
109
95
  version_requirements: !ruby/object:Gem::Requirement
110
96
  requirements:
111
- - - ">="
112
- - !ruby/object:Gem::Version
113
- version: '0'
114
- - !ruby/object:Gem::Dependency
115
- name: bundler
116
- requirement: !ruby/object:Gem::Requirement
117
- requirements:
118
- - - ">="
119
- - !ruby/object:Gem::Version
120
- version: '0'
121
- type: :development
122
- prerelease: false
123
- version_requirements: !ruby/object:Gem::Requirement
124
- requirements:
125
- - - ">="
126
- - !ruby/object:Gem::Version
127
- version: '0'
128
- - !ruby/object:Gem::Dependency
129
- name: pry
130
- requirement: !ruby/object:Gem::Requirement
131
- requirements:
132
- - - ">="
133
- - !ruby/object:Gem::Version
134
- version: '0'
135
- type: :development
136
- prerelease: false
137
- version_requirements: !ruby/object:Gem::Requirement
138
- requirements:
139
- - - ">="
140
- - !ruby/object:Gem::Version
141
- version: '0'
142
- - !ruby/object:Gem::Dependency
143
- name: rake
144
- requirement: !ruby/object:Gem::Requirement
145
- requirements:
146
- - - ">="
147
- - !ruby/object:Gem::Version
148
- version: '0'
149
- type: :development
150
- prerelease: false
151
- version_requirements: !ruby/object:Gem::Requirement
152
- requirements:
153
- - - ">="
154
- - !ruby/object:Gem::Version
155
- version: '0'
156
- - !ruby/object:Gem::Dependency
157
- name: rspec
158
- requirement: !ruby/object:Gem::Requirement
159
- requirements:
160
- - - ">="
161
- - !ruby/object:Gem::Version
162
- version: '0'
163
- type: :development
164
- prerelease: false
165
- version_requirements: !ruby/object:Gem::Requirement
166
- requirements:
167
- - - ">="
97
+ - - "~>"
168
98
  - !ruby/object:Gem::Version
169
- version: '0'
99
+ version: '1.2'
170
100
  description: Apply simple edits to images
171
101
  email:
172
102
  - git@intersect-uk.co.uk
173
103
  executables: []
174
- extensions: []
104
+ extensions:
105
+ - ext/morandi_native/extconf.rb
106
+ - ext/gdk_pixbuf_cairo/extconf.rb
175
107
  extra_rdoc_files: []
176
108
  files:
177
- - ".gitignore"
178
- - ".rspec"
179
- - ".ruby-version"
180
109
  - CHANGELOG.md
181
- - Gemfile
182
110
  - LICENSE.txt
183
111
  - README.md
184
- - Rakefile
112
+ - ext/gdk_pixbuf_cairo/extconf.rb
113
+ - ext/gdk_pixbuf_cairo/gdk_pixbuf_cairo.c
114
+ - ext/morandi_native/extconf.rb
115
+ - ext/morandi_native/filter.h
116
+ - ext/morandi_native/gamma.h
117
+ - ext/morandi_native/mask.h
118
+ - ext/morandi_native/morandi_native.c
119
+ - ext/morandi_native/rotate.h
120
+ - ext/morandi_native/tint.h
121
+ - lib/gdk_pixbuf_cairo.so
185
122
  - lib/morandi.rb
186
- - lib/morandi/image-ops.rb
123
+ - lib/morandi/cairo_ext.rb
124
+ - lib/morandi/crop_utils.rb
125
+ - lib/morandi/errors.rb
126
+ - lib/morandi/image_operation.rb
187
127
  - lib/morandi/image_processor.rb
128
+ - lib/morandi/operation/colourify.rb
129
+ - lib/morandi/operation/image_border.rb
130
+ - lib/morandi/operation/straighten.rb
131
+ - lib/morandi/pixbuf_ext.rb
188
132
  - lib/morandi/profiled_pixbuf.rb
189
133
  - lib/morandi/redeye.rb
190
- - lib/morandi/utils.rb
134
+ - lib/morandi/srgb_conversion.rb
191
135
  - lib/morandi/version.rb
192
- - morandi.gemspec
193
- - sample/sample.jpg
194
- - spec/morandi_spec.rb
195
- - spec/spec_helper.rb
196
- homepage: ''
136
+ - lib/morandi_native.so
137
+ homepage: https://github.com/livelink/morandi-rb
197
138
  licenses:
198
139
  - MIT
199
- metadata: {}
140
+ metadata:
141
+ source_code_uri: https://github.com/livelink/morandi-rb
142
+ rubygems_mfa_required: 'true'
200
143
  post_install_message:
201
144
  rdoc_options: []
202
145
  require_paths:
@@ -205,7 +148,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
205
148
  requirements:
206
149
  - - ">="
207
150
  - !ruby/object:Gem::Version
208
- version: '0'
151
+ version: '2.7'
209
152
  required_rubygems_version: !ruby/object:Gem::Requirement
210
153
  requirements:
211
154
  - - ">="
@@ -216,7 +159,5 @@ rubygems_version: 3.1.2
216
159
  signing_key:
217
160
  specification_version: 4
218
161
  summary: Simple Image Edits
219
- test_files:
220
- - spec/morandi_spec.rb
221
- - spec/spec_helper.rb
162
+ test_files: []
222
163
  ...
data/.gitignore DELETED
@@ -1,18 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .DS_Store
5
- .config
6
- .yardoc
7
- Gemfile.lock
8
- InstalledFiles
9
- _yardoc
10
- coverage
11
- doc/
12
- lib/bundler/man
13
- pkg
14
- rdoc
15
- spec/reports
16
- test/tmp
17
- test/version_tmp
18
- tmp
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --color
2
- --format progress
3
- --require spec_helper
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 2.7.1
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in morandi.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1 +0,0 @@
1
- require "bundler/gem_tasks"