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.
@@ -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"