morandi 0.99.4 → 0.100.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 +4 -4
- data/CHANGELOG.md +16 -1
- data/lib/gdk_pixbuf_cairo.so +0 -0
- data/lib/morandi/crop_utils.rb +34 -0
- data/lib/morandi/operation/image_border.rb +3 -9
- data/lib/morandi/operation/vips_straighten.rb +60 -0
- data/lib/morandi/version.rb +1 -1
- data/lib/morandi/vips_image_processor.rb +194 -0
- data/lib/morandi.rb +22 -5
- data/lib/morandi_native.so +0 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ca1f4a637e59ee90de4f309dfecc40eae27b103f416ea301af36e8bd04c555f
|
4
|
+
data.tar.gz: 8ebeda3024e6275ea7e8b75c8357339523a59a24987e1ebd07142cba445edce7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55308d1a2ae4626a81bf1faa9a6ae0535f816d777e31d25266e36e563bdc1dc60c55ef588ade477ce113a13bd20f80aefd046a52c1e4aaccffdc103e48d75cac
|
7
|
+
data.tar.gz: c10ab8cbe21121d50a2c0a10cdb6c0fefe595962d11cf936adeaef78e8291bd4688b37bb464577c7f455d8aa02e04def65d46930a86fb3cbf9c1aed4e9abf972
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,22 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [0.
|
7
|
+
## [0.100.0] 17.01.2024
|
8
|
+
### Added
|
9
|
+
- Vips image processor (resizing)
|
10
|
+
- Vips colour filters
|
11
|
+
- Vips crop
|
12
|
+
- Vips rotate
|
13
|
+
- Vips straighten
|
14
|
+
- Vips gamma
|
15
|
+
- Vips stripping alpha
|
16
|
+
- Explicit error when trying to use VipsProcessor with unsupported options
|
17
|
+
- Vips cast to srgb when image uses a different colourspace
|
18
|
+
|
19
|
+
### Removed
|
20
|
+
- [BREAKING] dropped support for a broken 'dominant' border colour
|
21
|
+
|
22
|
+
## [0.99.4] 22.11.2024
|
8
23
|
### Added
|
9
24
|
- Better test coverage for straighten operation
|
10
25
|
- Support for visual image comparison in specs
|
data/lib/gdk_pixbuf_cairo.so
CHANGED
Binary file
|
data/lib/morandi/crop_utils.rb
CHANGED
@@ -91,5 +91,39 @@ module Morandi
|
|
91
91
|
end
|
92
92
|
pixbuf
|
93
93
|
end
|
94
|
+
|
95
|
+
def apply_crop_vips(img, x_coord, y_coord, width, height)
|
96
|
+
if x_coord.negative? ||
|
97
|
+
y_coord.negative? ||
|
98
|
+
((x_coord + width) > img.width) ||
|
99
|
+
((y_coord + height) > img.height)
|
100
|
+
|
101
|
+
extract_area_x = [0, x_coord].max
|
102
|
+
extract_area_y = [0, y_coord].max
|
103
|
+
area_to_copy = img.extract_area(extract_area_x, extract_area_y, img.width - extract_area_x,
|
104
|
+
img.height - extract_area_y)
|
105
|
+
|
106
|
+
fill_colour = [255, 255, 255]
|
107
|
+
pixel = (Vips::Image.black(1, 1).colourspace(:srgb) + fill_colour).cast(img.format)
|
108
|
+
canvas = pixel.embed 0, 0, width, height, extend: :copy
|
109
|
+
|
110
|
+
cropped = canvas.composite(area_to_copy, :over, x: [-x_coord, 0].max,
|
111
|
+
y: [-y_coord, 0].max,
|
112
|
+
compositing_space: area_to_copy.interpretation)
|
113
|
+
|
114
|
+
# Because image is drawn on an opaque white, alpha doesn't matter at this point anyway, so let's strip the
|
115
|
+
# alpha channel from the output. According to #composite docs, the resulting image always has alpha channel,
|
116
|
+
# but I added a guard to avoid regressions if that ever changes.
|
117
|
+
cropped = cropped.extract_band(0, n: cropped.bands - 1) if cropped.has_alpha?
|
118
|
+
cropped
|
119
|
+
else
|
120
|
+
x_coord = x_coord.clamp(0, img.width)
|
121
|
+
y_coord = y_coord.clamp(0, img.height)
|
122
|
+
width = width.clamp(1, img.width - x_coord)
|
123
|
+
height = height.clamp(1, img.height - y_coord)
|
124
|
+
|
125
|
+
img.crop(x_coord, y_coord, width, height)
|
126
|
+
end
|
127
|
+
end
|
94
128
|
end
|
95
129
|
end
|
@@ -7,7 +7,7 @@ module Morandi
|
|
7
7
|
module Operation
|
8
8
|
# Image Border operation
|
9
9
|
# Supports retro (rounded) and square borders
|
10
|
-
# Background colour (ie. border colour) can be white, black
|
10
|
+
# Background colour (ie. border colour) can be white, black
|
11
11
|
# @!visibility private
|
12
12
|
class ImageBorder < ImageOperation
|
13
13
|
attr_accessor :style, :colour, :crop, :size, :print_size, :shrink, :border_size
|
@@ -26,7 +26,7 @@ module Morandi
|
|
26
26
|
|
27
27
|
@border_scale = [img_width, img_height].max.to_f / print_size.max.to_i
|
28
28
|
|
29
|
-
draw_background(cr, img_height, img_width
|
29
|
+
draw_background(cr, img_height, img_width)
|
30
30
|
|
31
31
|
x = border_width
|
32
32
|
y = border_width
|
@@ -75,7 +75,7 @@ module Morandi
|
|
75
75
|
cr.paint(1.0)
|
76
76
|
end
|
77
77
|
|
78
|
-
def draw_background(cr, img_height, img_width
|
78
|
+
def draw_background(cr, img_height, img_width)
|
79
79
|
cr.save do
|
80
80
|
cr.translate(-@crop[0], -@crop[1]) if @crop && ((@crop[0]).negative? || (@crop[1]).negative?)
|
81
81
|
|
@@ -86,12 +86,6 @@ module Morandi
|
|
86
86
|
|
87
87
|
cr.rectangle(0, 0, img_width, img_height)
|
88
88
|
case colour
|
89
|
-
when 'dominant'
|
90
|
-
pixbuf.scale_max(400).save(fn = "/tmp/hist-#{$PROCESS_ID}.#{Time.now.to_i}", 'jpeg')
|
91
|
-
histogram = Colorscore::Histogram.new(fn)
|
92
|
-
FileUtils.rm_f(fn)
|
93
|
-
col = histogram.scores.first[1]
|
94
|
-
cr.set_source_rgb col.red / 256.0, col.green / 256.0, col.blue / 256.0
|
95
89
|
when 'retro'
|
96
90
|
cr.set_source_rgb 1, 1, 0.8
|
97
91
|
when 'black'
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Morandi
|
4
|
+
module Operation
|
5
|
+
# Straighten operation
|
6
|
+
# Does a small (ie. not 90,180,270 deg) rotation and zooms to avoid cropping
|
7
|
+
# @!visibility private
|
8
|
+
class VipsStraighten < ImageOperation
|
9
|
+
# Colour for filling background post-rotation. It can bleed into the edge pixels during resize.
|
10
|
+
# Setting it to gray minimises the average impact
|
11
|
+
ROTATION_BACKGROUND_FILL_COLOUR = 127
|
12
|
+
ROTATION_BACKGROUND_FILL_ALPHA = 255
|
13
|
+
|
14
|
+
def self.rotation_background_fill_colour(channels_count:, alpha:)
|
15
|
+
return [ROTATION_BACKGROUND_FILL_COLOUR] * channels_count unless alpha # Eg [127, 127, 127] for RGB
|
16
|
+
|
17
|
+
# Eg [127, 127, 127, 255] for RGBA
|
18
|
+
([ROTATION_BACKGROUND_FILL_COLOUR] * (channels_count - 1)) + [ROTATION_BACKGROUND_FILL_ALPHA]
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :angle
|
22
|
+
|
23
|
+
def call(img)
|
24
|
+
return img if angle.zero?
|
25
|
+
|
26
|
+
original_width = img.width
|
27
|
+
original_height = img.height
|
28
|
+
|
29
|
+
# It is possible to first rotate, then fetch width/height of resulting image to calculate scale,
|
30
|
+
# but that would make us lose precision which degrades cropping accuracy
|
31
|
+
rotation_value_rad = angle * (Math::PI / 180)
|
32
|
+
post_rotation_bounding_box_width = (img.height.to_f * Math.sin(rotation_value_rad).abs) +
|
33
|
+
(img.width.to_f * Math.cos(rotation_value_rad).abs)
|
34
|
+
post_rotation_bounding_box_height = (img.width.to_f * Math.sin(rotation_value_rad).abs) +
|
35
|
+
(img.height.to_f * Math.cos(rotation_value_rad).abs)
|
36
|
+
|
37
|
+
# Calculate scaling required to fit the original width/height within rotated image without including background
|
38
|
+
scale = [post_rotation_bounding_box_width / original_width,
|
39
|
+
post_rotation_bounding_box_height / original_height].max
|
40
|
+
|
41
|
+
background_fill_colour = self.class.rotation_background_fill_colour(channels_count: img.bands,
|
42
|
+
alpha: img.has_alpha?)
|
43
|
+
img = img.similarity(angle: angle, scale: scale, background: background_fill_colour)
|
44
|
+
|
45
|
+
# Better precision than img.width/img.height due to fractions preservation
|
46
|
+
post_scale_bounding_box_width = post_rotation_bounding_box_width * scale
|
47
|
+
post_scale_bounding_box_height = post_rotation_bounding_box_height * scale
|
48
|
+
|
49
|
+
width_diff = post_scale_bounding_box_width - original_width
|
50
|
+
height_diff = post_scale_bounding_box_height - original_height
|
51
|
+
|
52
|
+
# Round to nearest integer to reduce risk of ROTATION_BACKGROUND_FILL_COLOUR being visible in the corner
|
53
|
+
crop_x = (width_diff / 2).round
|
54
|
+
crop_y = (height_diff / 2).round
|
55
|
+
|
56
|
+
img.crop(crop_x, crop_y, original_width, original_height)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/morandi/version.rb
CHANGED
@@ -0,0 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'vips'
|
4
|
+
|
5
|
+
require 'morandi/srgb_conversion'
|
6
|
+
require 'morandi/operation/vips_straighten'
|
7
|
+
|
8
|
+
module Morandi
|
9
|
+
# An alternative to ImageProcessor which is based on libvips for concurrent and less memory-intensive processing
|
10
|
+
class VipsImageProcessor
|
11
|
+
# Colour filter related constants
|
12
|
+
RGB_LUMINANCE_EXTRACTION_FACTORS = [0.3086, 0.6094, 0.0820].freeze
|
13
|
+
SEPIA_MODIFIER = [25, 5, -25].freeze
|
14
|
+
BLUETONE_MODIFIER = [-10, 5, 25].freeze
|
15
|
+
COLOUR_FILTER_MODIFIERS = {
|
16
|
+
'sepia' => SEPIA_MODIFIER,
|
17
|
+
'bluetone' => BLUETONE_MODIFIER
|
18
|
+
}.freeze
|
19
|
+
SUPPORTED_FILTERS = COLOUR_FILTER_MODIFIERS.keys + ['greyscale']
|
20
|
+
|
21
|
+
def self.supports?(input, options)
|
22
|
+
return false unless input.is_a?(String)
|
23
|
+
return false if options['brighten'].to_f != 0
|
24
|
+
return false if options['contrast'].to_f != 0
|
25
|
+
return false if options['sharpen'].to_f != 0
|
26
|
+
return false if options['redeye']&.any?
|
27
|
+
return false if options['border-style']
|
28
|
+
return false if options['background-style']
|
29
|
+
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Vips options are global, this method sets them for yielding, then restores to original
|
34
|
+
def self.with_global_options(cache_max:, concurrency:)
|
35
|
+
previous_cache_max = Vips.cache_max
|
36
|
+
previous_concurrency = Vips.concurrency
|
37
|
+
|
38
|
+
Vips.cache_set_max(cache_max)
|
39
|
+
Vips.concurrency_set(concurrency)
|
40
|
+
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
Vips.cache_set_max(previous_cache_max)
|
44
|
+
Vips.concurrency_set(previous_concurrency)
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(path, user_options)
|
48
|
+
@path = path
|
49
|
+
|
50
|
+
@options = user_options
|
51
|
+
|
52
|
+
@size_limit_on_load_px = @options['output.max']
|
53
|
+
@output_width = @options['output.width']
|
54
|
+
@output_height = @options['output.height']
|
55
|
+
end
|
56
|
+
|
57
|
+
def process!
|
58
|
+
source_file_path = Morandi::SrgbConversion.perform(@path) || @path
|
59
|
+
begin
|
60
|
+
@img = Vips::Image.new_from_file(source_file_path)
|
61
|
+
rescue Vips::Error => e
|
62
|
+
# Match the known errors
|
63
|
+
raise UnknownTypeError if /is not a known file format/.match?(e.message)
|
64
|
+
raise CorruptImageError if /Premature end of JPEG file/.match?(e.message)
|
65
|
+
|
66
|
+
# Re-raise generic Error when unknown
|
67
|
+
raise Error, e.message
|
68
|
+
end
|
69
|
+
if @size_limit_on_load_px
|
70
|
+
@scale = @size_limit_on_load_px.to_f / [@img.width, @img.height].max
|
71
|
+
@img = @img.resize(@scale) if not_equal_to_one(@scale)
|
72
|
+
else
|
73
|
+
@scale = 1.0
|
74
|
+
end
|
75
|
+
|
76
|
+
apply_gamma!
|
77
|
+
apply_rotate!
|
78
|
+
apply_crop!
|
79
|
+
apply_filters!
|
80
|
+
|
81
|
+
if @options['output.limit'] && @output_width && @output_height
|
82
|
+
scale_factor = [@output_width, @output_height].max.to_f / [@img.width, @img.height].max
|
83
|
+
@img = @img.resize(scale_factor) if scale_factor < 1.0
|
84
|
+
end
|
85
|
+
|
86
|
+
strip_alpha!
|
87
|
+
ensure_srgb!
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_to_png(_write_to, _orientation = :any)
|
91
|
+
raise 'not implemented'
|
92
|
+
end
|
93
|
+
|
94
|
+
def write_to_jpeg(target_path, quality = nil)
|
95
|
+
process!
|
96
|
+
|
97
|
+
quality ||= @options.fetch('quality', 97)
|
98
|
+
|
99
|
+
target_path_jpg = "#{target_path}.jpg" # Vips chooses format based on file extension, this ensures jpg
|
100
|
+
@img.write_to_file(target_path_jpg, Q: quality)
|
101
|
+
FileUtils.mv(target_path_jpg, target_path)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Remove the alpha channel if present. Vips supports alpha, but the current Pixbuf processor happens to strip it in
|
107
|
+
# most cases (straighten and cropping beyond image bounds are exceptions)
|
108
|
+
#
|
109
|
+
# Alternatively, alpha can be left intact for more accurate processing and transparent output or merged into an
|
110
|
+
# image using Vips::Image#flatten for less resource-intensive processing
|
111
|
+
def strip_alpha!
|
112
|
+
@img = @img.extract_band(0, n: @img.bands - 1) if @img.has_alpha?
|
113
|
+
end
|
114
|
+
|
115
|
+
def apply_gamma!
|
116
|
+
return unless @options['gamma'] && not_equal_to_one(@options['gamma'])
|
117
|
+
|
118
|
+
@img = @img.gamma(exponent: @options['gamma'])
|
119
|
+
end
|
120
|
+
|
121
|
+
def angle
|
122
|
+
@options['angle'].to_i % 360
|
123
|
+
end
|
124
|
+
|
125
|
+
def apply_rotate!
|
126
|
+
@img = case angle
|
127
|
+
when 0 then @img
|
128
|
+
when 90 then @img.rot90
|
129
|
+
when 180 then @img.rot180
|
130
|
+
when 270 then @img.rot270
|
131
|
+
else raise('"angle" option only accepts multiples of 90')
|
132
|
+
end
|
133
|
+
|
134
|
+
unless @options['straighten'].to_f.zero?
|
135
|
+
@img = Morandi::Operation::VipsStraighten.new_from_hash(angle: @options['straighten'].to_f).call(@img)
|
136
|
+
end
|
137
|
+
|
138
|
+
@image_width = @img.width
|
139
|
+
@image_height = @img.height
|
140
|
+
end
|
141
|
+
|
142
|
+
def apply_crop!
|
143
|
+
crop = @options['crop']
|
144
|
+
|
145
|
+
return if crop.nil? && @options['image.auto-crop'].eql?(false)
|
146
|
+
|
147
|
+
crop = crop.split(',').map(&:to_i) if crop.is_a?(String) && crop =~ /^\d+,\d+,\d+,\d+/
|
148
|
+
|
149
|
+
crop = nil unless crop.is_a?(Array) && crop.size.eql?(4) && crop.all? do |i|
|
150
|
+
i.is_a?(Numeric)
|
151
|
+
end
|
152
|
+
# can't crop, won't crop
|
153
|
+
return if @output_width.nil? && @output_height.nil? && crop.nil?
|
154
|
+
|
155
|
+
crop = crop.map { |s| (s.to_f * @scale).floor } if crop && not_equal_to_one(@scale)
|
156
|
+
crop ||= Morandi::CropUtils.autocrop_coords(@img.width, @img.height, @output_width, @output_height)
|
157
|
+
@img = Morandi::CropUtils.apply_crop_vips(@img, crop[0], crop[1], crop[2], crop[3])
|
158
|
+
end
|
159
|
+
|
160
|
+
def apply_filters!
|
161
|
+
filter_name = @options['fx']
|
162
|
+
return unless SUPPORTED_FILTERS.include?(filter_name)
|
163
|
+
|
164
|
+
# The filter-related constants assume RGB colourspace, so it requires early conversion
|
165
|
+
ensure_srgb!
|
166
|
+
|
167
|
+
# Convert to greyscale using weights
|
168
|
+
rgb_factors = RGB_LUMINANCE_EXTRACTION_FACTORS
|
169
|
+
recombination_matrix = [rgb_factors, rgb_factors, rgb_factors]
|
170
|
+
if @img.has_alpha?
|
171
|
+
# Add "0" multiplier for alpha to ignore it for luminance calculation
|
172
|
+
recombination_matrix = recombination_matrix.map { |channel_multipliers| channel_multipliers + [0] }
|
173
|
+
# Add fourth row in the matrix to preserve unchanged alpha channel
|
174
|
+
recombination_matrix << [0, 0, 0, 1]
|
175
|
+
end
|
176
|
+
@img = @img.recomb(recombination_matrix)
|
177
|
+
|
178
|
+
return unless COLOUR_FILTER_MODIFIERS[filter_name]
|
179
|
+
|
180
|
+
# Apply colour adjustment based on the modifiers setup
|
181
|
+
colour_filter_modifier = COLOUR_FILTER_MODIFIERS[filter_name]
|
182
|
+
colour_filter_modifier += [0] if @img.has_alpha?
|
183
|
+
@img = @img.linear(1.0, colour_filter_modifier)
|
184
|
+
end
|
185
|
+
|
186
|
+
def not_equal_to_one(float)
|
187
|
+
(float - 1.0).abs >= Float::EPSILON
|
188
|
+
end
|
189
|
+
|
190
|
+
def ensure_srgb!
|
191
|
+
@img = @img.colourspace(:srgb) unless @img.interpretation == :srgb
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/lib/morandi.rb
CHANGED
@@ -7,6 +7,7 @@ require 'morandi/cairo_ext'
|
|
7
7
|
require 'morandi/pixbuf_ext'
|
8
8
|
require 'morandi/errors'
|
9
9
|
require 'morandi/image_processor'
|
10
|
+
require 'morandi/vips_image_processor'
|
10
11
|
require 'morandi/redeye'
|
11
12
|
require 'morandi/crop_utils'
|
12
13
|
|
@@ -22,12 +23,13 @@ module Morandi
|
|
22
23
|
# @option options [Float] 'gamma' Gamma correct image
|
23
24
|
# @option options [Integer] 'contrast' Change image contrast (-20..20)
|
24
25
|
# @option options [Integer] 'sharpen' Sharpen (1..5) / Blur (-1..-5)
|
26
|
+
# @option options [Integer] 'straighten' Rotate by a small angle (in degrees) and zoom in to fill the size
|
25
27
|
# @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)
|
28
|
+
# @option options [Integer] 'angle' Rotate image clockwise by multiple of 90 degrees (0, 90, 180, 270)
|
27
29
|
# @option options [Array[Integer,Integer,Integer,Integer]] 'crop' Crop image (x, y, width, height)
|
28
30
|
# @option options [String] 'fx' Apply colour filters ('greyscale', 'sepia', 'bluetone')
|
29
31
|
# @option options [String] 'border-style' Set border style ('square', 'retro')
|
30
|
-
# @option options [String] 'background-style' Set border colour ('retro', 'black', 'white'
|
32
|
+
# @option options [String] 'background-style' Set border colour ('retro', 'black', 'white')
|
31
33
|
# @option options [Integer] 'quality' (97) Set JPG compression value (1 to 100)
|
32
34
|
# @option options [Integer] 'output.max' Downscales the image to fit within the square of given size before
|
33
35
|
# processing to limit the required resources
|
@@ -42,9 +44,24 @@ module Morandi
|
|
42
44
|
# @param target_path [String] target location for image
|
43
45
|
# @param local_options [Hash] Hash of options other than desired transformations
|
44
46
|
# @option local_options [String] 'path.icc' A path to store the input after converting to sRGB colour space
|
47
|
+
# @option local_options [String] 'processor' ('pixbuf') Name of the image processing library ('pixbuf', 'vips')
|
48
|
+
# NOTE: vips processor only handles subset of operations,
|
49
|
+
# see `Morandi::VipsImageProcessor.supports?` for details
|
45
50
|
def process(source, options, target_path, local_options = {})
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
case local_options['processor']
|
52
|
+
when 'vips'
|
53
|
+
raise(ArgumentError, 'Requested unsupported Vips operation') unless VipsImageProcessor.supports?(source, options)
|
54
|
+
|
55
|
+
# Cache saves time in expense of RAM when performing the same processing multiple times
|
56
|
+
# Cache is also created for files based on their names, which can lead to leaking files data, so in terms
|
57
|
+
# of security it feels prudent to disable it. Latest libvips supports "revalidate" option to prevent that risk
|
58
|
+
cache_max = 0
|
59
|
+
concurrency = 2 # Hardcoding to 2 for now to maintain some balance between resource usage and performance
|
60
|
+
VipsImageProcessor.with_global_options(cache_max: cache_max, concurrency: concurrency) do
|
61
|
+
VipsImageProcessor.new(source, options).write_to_jpeg(target_path)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
ImageProcessor.new(source, options, local_options).tap(&:result).write_to_jpeg(target_path)
|
65
|
+
end
|
49
66
|
end
|
50
67
|
end
|
data/lib/morandi_native.so
CHANGED
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.
|
4
|
+
version: 0.100.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:
|
14
|
+
date: 2025-01-17 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: atk
|
@@ -97,6 +97,20 @@ dependencies:
|
|
97
97
|
- - "~>"
|
98
98
|
- !ruby/object:Gem::Version
|
99
99
|
version: '1.2'
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: ruby-vips
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
100
114
|
description: Apply simple edits to images
|
101
115
|
email:
|
102
116
|
- git@intersect-uk.co.uk
|
@@ -128,11 +142,13 @@ files:
|
|
128
142
|
- lib/morandi/operation/colourify.rb
|
129
143
|
- lib/morandi/operation/image_border.rb
|
130
144
|
- lib/morandi/operation/straighten.rb
|
145
|
+
- lib/morandi/operation/vips_straighten.rb
|
131
146
|
- lib/morandi/pixbuf_ext.rb
|
132
147
|
- lib/morandi/profiled_pixbuf.rb
|
133
148
|
- lib/morandi/redeye.rb
|
134
149
|
- lib/morandi/srgb_conversion.rb
|
135
150
|
- lib/morandi/version.rb
|
151
|
+
- lib/morandi/vips_image_processor.rb
|
136
152
|
- lib/morandi_native.so
|
137
153
|
homepage: https://github.com/livelink/morandi-rb
|
138
154
|
licenses:
|