morandi 0.99.4 → 0.100.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|