morandi 0.12.0 → 0.99.03
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 +24 -1
- data/README.md +6 -1
- data/ext/gdk_pixbuf_cairo/extconf.rb +124 -0
- data/ext/gdk_pixbuf_cairo/gdk_pixbuf_cairo.c +260 -0
- data/ext/morandi_native/extconf.rb +121 -0
- data/ext/morandi_native/filter.h +229 -0
- data/ext/morandi_native/gamma.h +48 -0
- data/ext/morandi_native/mask.h +75 -0
- data/ext/morandi_native/morandi_native.c +1126 -0
- data/ext/morandi_native/rotate.h +79 -0
- data/ext/morandi_native/tint.h +72 -0
- data/lib/gdk_pixbuf_cairo.so +0 -0
- data/lib/morandi/cairo_ext.rb +56 -0
- data/lib/morandi/crop_utils.rb +95 -0
- data/lib/morandi/image_operation.rb +205 -0
- data/lib/morandi/image_processor.rb +180 -180
- data/lib/morandi/pixbuf_ext.rb +19 -0
- data/lib/morandi/profiled_pixbuf.rb +40 -61
- data/lib/morandi/redeye.rb +35 -47
- data/lib/morandi/version.rb +3 -1
- data/lib/morandi.rb +33 -11
- data/lib/morandi_native.so +0 -0
- metadata +52 -117
- data/.gitignore +0 -18
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/Gemfile +0 -4
- data/Rakefile +0 -1
- data/lib/morandi/image-ops.rb +0 -307
- data/lib/morandi/utils.rb +0 -136
- data/morandi.gemspec +0 -33
- data/sample/100_mb_image.jpg +0 -0
- data/sample/sample.jpg +0 -0
- data/spec/morandi_spec.rb +0 -208
- data/spec/spec_helper.rb +0 -19
data/lib/morandi/utils.rb
DELETED
@@ -1,136 +0,0 @@
|
|
1
|
-
require 'gdk_pixbuf2'
|
2
|
-
|
3
|
-
module Morandi
|
4
|
-
module Utils
|
5
|
-
module_function
|
6
|
-
def autocrop_coords(iw, ih, width, height)
|
7
|
-
return nil unless width
|
8
|
-
aspect = width.to_f / height.to_f
|
9
|
-
iaspect = iw.to_f / ih.to_f
|
10
|
-
|
11
|
-
if ih > iw
|
12
|
-
# Portrait image
|
13
|
-
# Check whether the aspect ratio is greater or smaller
|
14
|
-
# ie. where constraints will hit
|
15
|
-
aspect = height.to_f / width.to_f
|
16
|
-
end
|
17
|
-
|
18
|
-
# Landscape
|
19
|
-
if aspect > iaspect
|
20
|
-
# Width constraint - aspect-rect wider
|
21
|
-
crop_width = iw
|
22
|
-
crop_height = (crop_width / aspect).to_i
|
23
|
-
else
|
24
|
-
# Height constraint - aspect-rect wider
|
25
|
-
crop_height = ih
|
26
|
-
crop_width = (crop_height * aspect).to_i
|
27
|
-
end
|
28
|
-
|
29
|
-
[
|
30
|
-
((iw - crop_width)>>1),
|
31
|
-
((ih - crop_height)>>1),
|
32
|
-
crop_width,
|
33
|
-
crop_height
|
34
|
-
].map { |i| i.to_i }
|
35
|
-
end
|
36
|
-
|
37
|
-
def constrain(val,min,max)
|
38
|
-
if val < min
|
39
|
-
min
|
40
|
-
elsif val > max
|
41
|
-
max
|
42
|
-
else
|
43
|
-
val
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def apply_crop(pixbuf, x, y, w, h, fill_col = 0xffffffff)
|
48
|
-
if (x < 0) or (y < 0) || ((x+w) > pixbuf.width) || ((y+h) > pixbuf.height)
|
49
|
-
#tw, th = [w-x,w].max, [h-y,h].max
|
50
|
-
base_pixbuf = GdkPixbuf::Pixbuf.new(
|
51
|
-
colorspace: GdkPixbuf::Colorspace::RGB,
|
52
|
-
has_alpha: false,
|
53
|
-
bits_per_sample: 8,
|
54
|
-
width: w,
|
55
|
-
height: h
|
56
|
-
)
|
57
|
-
base_pixbuf.fill!(fill_col)
|
58
|
-
dest_x = [x, 0].min
|
59
|
-
dest_y = [y, 0].min
|
60
|
-
#src_x = [x,0].max
|
61
|
-
#src_y = [y,0].max
|
62
|
-
dest_x = [-x,0].max
|
63
|
-
dest_y = [-y,0].max
|
64
|
-
|
65
|
-
#if x < 0
|
66
|
-
#else
|
67
|
-
#end
|
68
|
-
#if y < 0
|
69
|
-
# dest_h = [h-dest_y, pixbuf.height, base_pixbuf.height-dest_y].min
|
70
|
-
#else
|
71
|
-
# dest_h = [h,pixbuf.height].min
|
72
|
-
#end
|
73
|
-
# dest_w = [w-dest_x, pixbuf.width, base_pixbuf.width-dest_x].min
|
74
|
-
|
75
|
-
offset_x = [x,0].max
|
76
|
-
offset_y = [y,0].max
|
77
|
-
copy_w = [w, pixbuf.width - offset_x].min
|
78
|
-
copy_h = [h, pixbuf.height - offset_y].min
|
79
|
-
|
80
|
-
paste_x = [x, 0].min * -1
|
81
|
-
paste_y = [y, 0].min * -1
|
82
|
-
|
83
|
-
if copy_w + paste_x > base_pixbuf.width
|
84
|
-
copy_w = base_pixbuf.width - paste_x
|
85
|
-
end
|
86
|
-
if copy_h + paste_y > base_pixbuf.height
|
87
|
-
copy_h = base_pixbuf.height - paste_y
|
88
|
-
end
|
89
|
-
base_pixbuf.composite! pixbuf, {
|
90
|
-
dest_x: paste_x,
|
91
|
-
dest_y: paste_y,
|
92
|
-
dest_width: copy_w,
|
93
|
-
dest_height: copy_h,
|
94
|
-
offset_x: paste_x - offset_x,
|
95
|
-
offset_y: paste_y - offset_y,
|
96
|
-
scale_x: 1,
|
97
|
-
scale_y: 1,
|
98
|
-
interpolation_type: :hyper,
|
99
|
-
overall_alpha: 255
|
100
|
-
}
|
101
|
-
pixbuf = base_pixbuf
|
102
|
-
else
|
103
|
-
x = constrain(x, 0, pixbuf.width)
|
104
|
-
y = constrain(y, 0, pixbuf.height)
|
105
|
-
w = constrain(w, 1, pixbuf.width - x)
|
106
|
-
h = constrain(h, 1, pixbuf.height - y)
|
107
|
-
#p [pixbuf, x, y, w, h]
|
108
|
-
pixbuf = pixbuf.subpixbuf(x, y, w, h)
|
109
|
-
end
|
110
|
-
pixbuf
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
class GdkPixbuf::Pixbuf
|
116
|
-
unless defined?(::GdkPixbuf::Pixbuf::InterpType)
|
117
|
-
InterpType = GdkPixbuf::InterpType
|
118
|
-
end
|
119
|
-
|
120
|
-
def scale_max(max_size, interp = GdkPixbuf::Pixbuf::InterpType::BILINEAR, max_scale = 1.0)
|
121
|
-
mul = (max_size / [width,height].max.to_f)
|
122
|
-
mul = [max_scale = 1.0,mul].min
|
123
|
-
scale(width * mul, height * mul, interp)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class Cairo::ImageSurface
|
128
|
-
def to_pixbuf
|
129
|
-
loader = GdkPixbuf::PixbufLoader.new
|
130
|
-
io = StringIO.new
|
131
|
-
write_to_png(io)
|
132
|
-
io.rewind
|
133
|
-
loader.last_write(io.read)
|
134
|
-
loader.pixbuf
|
135
|
-
end
|
136
|
-
end
|
data/morandi.gemspec
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'morandi/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "morandi"
|
8
|
-
spec.version = Morandi::VERSION
|
9
|
-
spec.authors = ["Geoff Youngs\n\n\n"]
|
10
|
-
spec.email = ["git@intersect-uk.co.uk"]
|
11
|
-
spec.summary = %q{Simple Image Edits}
|
12
|
-
spec.description = %q{Apply simple edits to images}
|
13
|
-
spec.homepage = ""
|
14
|
-
spec.license = "MIT"
|
15
|
-
|
16
|
-
spec.files = `git ls-files -z`.split("\x0")
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = ["lib"]
|
20
|
-
|
21
|
-
spec.add_dependency "gtk2"
|
22
|
-
spec.add_dependency "gdk_pixbuf2", "~> 3.4.0"
|
23
|
-
spec.add_dependency "cairo"
|
24
|
-
spec.add_dependency "pixbufutils"
|
25
|
-
spec.add_dependency "redeye"
|
26
|
-
spec.add_dependency "pango"
|
27
|
-
spec.add_dependency "colorscore"
|
28
|
-
|
29
|
-
spec.add_development_dependency "bundler"
|
30
|
-
spec.add_development_dependency "pry"
|
31
|
-
spec.add_development_dependency "rake"
|
32
|
-
spec.add_development_dependency "rspec"
|
33
|
-
end
|
data/sample/100_mb_image.jpg
DELETED
Binary file
|
data/sample/sample.jpg
DELETED
Binary file
|
data/spec/morandi_spec.rb
DELETED
@@ -1,208 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'fileutils'
|
4
|
-
require 'morandi'
|
5
|
-
|
6
|
-
RSpec.describe Morandi, '#process' do
|
7
|
-
context 'in command mode' do
|
8
|
-
it 'should create ouptut' do
|
9
|
-
Morandi.process('sample/sample.jpg', {}, out = 'sample/out_plain.jpg')
|
10
|
-
expect(File.exist?(out))
|
11
|
-
end
|
12
|
-
|
13
|
-
context "with a big image and a bigger cropped area to fill" do
|
14
|
-
it 'should create ouptut' do
|
15
|
-
settings = {
|
16
|
-
"crop"=>"0,477,15839,18804",
|
17
|
-
"angle"=>90,
|
18
|
-
"fx"=>"colour",
|
19
|
-
"straighten"=>0.0,
|
20
|
-
"gamma"=>0.98,
|
21
|
-
"redeye"=>[]
|
22
|
-
}
|
23
|
-
|
24
|
-
Morandi.process('sample/100_mb_image.jpg', settings, out = 'sample/out_100_mb_image.jpg')
|
25
|
-
expect(File.exist?(out))
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'should do rotation of images' do
|
30
|
-
original = GdkPixbuf::Pixbuf.get_file_info('sample/sample.jpg')
|
31
|
-
Morandi.process('sample/sample.jpg', {
|
32
|
-
'angle' => 90
|
33
|
-
}, out = 'sample/out_rotate90.jpg')
|
34
|
-
expect(File.exist?(out))
|
35
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
36
|
-
expect(original[1]).to eq(height)
|
37
|
-
expect(original[2]).to eq(width)
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'should accept pixbufs as an argument' do
|
41
|
-
pixbuf = GdkPixbuf::Pixbuf.new(file: 'sample/sample.jpg')
|
42
|
-
pro = Morandi::ImageProcessor.new(pixbuf, {}, {})
|
43
|
-
pro.process!
|
44
|
-
expect(pixbuf.width).to eq(pro.result.width)
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'should do cropping of images' do
|
48
|
-
Morandi.process('sample/sample.jpg', {
|
49
|
-
'crop' => [10, 10, 300, 300]
|
50
|
-
}, out = 'sample/out_crop.jpg')
|
51
|
-
expect(File.exist?(out))
|
52
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
53
|
-
expect(width).to eq(300)
|
54
|
-
expect(height).to eq(300)
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'should use user supplied path.icc' do
|
58
|
-
src = 'sample/sample.jpg'
|
59
|
-
icc = '/tmp/this-is-secure-thing.jpg'
|
60
|
-
default_icc = Morandi::ImageProcessor.default_icc_path(src)
|
61
|
-
out = 'sample/out_icc.jpg'
|
62
|
-
FileUtils.rm_f(default_icc)
|
63
|
-
Morandi.process(src, {}, out, 'path.icc' => icc)
|
64
|
-
expect(File).to exist(icc)
|
65
|
-
expect(File).not_to exist(default_icc)
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'should ignore user supplied path.icc' do
|
69
|
-
src = 'sample/sample.jpg'
|
70
|
-
icc = '/tmp/this-is-insecure-thing.jpg'
|
71
|
-
default_icc = Morandi::ImageProcessor.default_icc_path(src)
|
72
|
-
FileUtils.rm_f(icc)
|
73
|
-
FileUtils.rm_f(default_icc)
|
74
|
-
out = 'sample/out_icc.jpg'
|
75
|
-
Morandi.process(src, { 'path.icc' => icc, 'output.max' => 200 }, out)
|
76
|
-
expect(File).not_to exist(icc)
|
77
|
-
expect(File).to exist(default_icc)
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'should do cropping of images with a string' do
|
81
|
-
Morandi.process('sample/sample.jpg', {
|
82
|
-
'crop' => '10,10,300,300'
|
83
|
-
}, out = 'sample/out_crop.jpg')
|
84
|
-
expect(File.exist?(out))
|
85
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
86
|
-
expect(width).to eq(300)
|
87
|
-
expect(height).to eq(300)
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'should reduce the size of images' do
|
91
|
-
Morandi.process('sample/sample.jpg', {
|
92
|
-
'output.max' => 200
|
93
|
-
}, out = 'sample/out_reduce.jpg')
|
94
|
-
expect(File.exist?(out))
|
95
|
-
_, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
96
|
-
expect(width).to be <= 200
|
97
|
-
expect(height).to be <= 200
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'should reduce the straighten images' do
|
101
|
-
Morandi.process('sample/sample.jpg', {
|
102
|
-
'straighten' => 5
|
103
|
-
}, out = 'sample/out_straighten.jpg')
|
104
|
-
expect(File.exist?(out))
|
105
|
-
info, _, _ = GdkPixbuf::Pixbuf.get_file_info(out)
|
106
|
-
expect(info.name).to eq('jpeg')
|
107
|
-
end
|
108
|
-
|
109
|
-
it 'should reduce the gamma correct images' do
|
110
|
-
Morandi.process('sample/sample.jpg', {
|
111
|
-
'gamma' => 1.2
|
112
|
-
}, out = 'sample/out_gamma.jpg')
|
113
|
-
expect(File.exist?(out))
|
114
|
-
info, _, _ = GdkPixbuf::Pixbuf.get_file_info(out)
|
115
|
-
expect(info.name).to eq('jpeg')
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'should reduce the size of images' do
|
119
|
-
Morandi.process('sample/sample.jpg', {
|
120
|
-
'fx' => 'sepia'
|
121
|
-
}, out = 'sample/out_sepia.jpg')
|
122
|
-
expect(File.exist?(out))
|
123
|
-
info, _, _ = GdkPixbuf::Pixbuf.get_file_info(out)
|
124
|
-
expect(info.name).to eq('jpeg')
|
125
|
-
end
|
126
|
-
|
127
|
-
it 'should output at the specified size' do
|
128
|
-
Morandi.process('sample/sample.jpg', {
|
129
|
-
'output.width' => 300,
|
130
|
-
'output.height' => 200,
|
131
|
-
'image.auto-crop' => true,
|
132
|
-
'output.limit' => true
|
133
|
-
}, out = 'sample/out_at_size.jpg')
|
134
|
-
expect(File.exist?(out))
|
135
|
-
info, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
136
|
-
expect(info.name).to eq('jpeg')
|
137
|
-
expect(width).to be <= 300
|
138
|
-
expect(height).to be <= 200
|
139
|
-
end
|
140
|
-
|
141
|
-
it 'should blur the image' do
|
142
|
-
Morandi.process('sample/sample.jpg', {
|
143
|
-
'sharpen' => -3
|
144
|
-
}, out = 'sample/out_blur.jpg')
|
145
|
-
expect(File.exist?(out))
|
146
|
-
end
|
147
|
-
|
148
|
-
it 'should apply a border and maintain the target size' do
|
149
|
-
Morandi.process('sample/sample.jpg', {
|
150
|
-
'border-style' => 'square',
|
151
|
-
'background-style' => 'dominant',
|
152
|
-
'border-size-mm' => 5,
|
153
|
-
'output.width' => 800,
|
154
|
-
'output.height' => 650
|
155
|
-
}, out = 'sample/out_border.jpg')
|
156
|
-
expect(File.exist?(out))
|
157
|
-
|
158
|
-
info, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
159
|
-
expect(info.name).to eq('jpeg')
|
160
|
-
expect(width).to eq 800
|
161
|
-
expect(height).to eq 650
|
162
|
-
end
|
163
|
-
|
164
|
-
it 'should apply multiple transformations' do
|
165
|
-
Morandi.process('sample/sample.jpg', {
|
166
|
-
'brighten' => 5,
|
167
|
-
'contrast' => 5,
|
168
|
-
'sharpen' => 2,
|
169
|
-
'fx' => 'greyscale',
|
170
|
-
'border-style' => 'solid',
|
171
|
-
'background-style' => '#00FF00',
|
172
|
-
'crop' => [50, 0, 750, 650],
|
173
|
-
'output.width' => 300,
|
174
|
-
'output.height' => 260,
|
175
|
-
'output.limit' => true
|
176
|
-
}, out = 'sample/out_various.jpg')
|
177
|
-
expect(File.exist?(out))
|
178
|
-
|
179
|
-
info, width, height = GdkPixbuf::Pixbuf.get_file_info(out)
|
180
|
-
expect(info.name).to eq('jpeg')
|
181
|
-
expect(width).to eq 300
|
182
|
-
expect(height).to eq 260
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
context 'with increasing quality settings' do
|
187
|
-
let(:max_quality_file_size) do
|
188
|
-
Morandi.process('sample/sample.jpg', { 'quality' => 100 }, 'sample/out-100.jpg')
|
189
|
-
File.size('sample/out-100.jpg')
|
190
|
-
end
|
191
|
-
|
192
|
-
let(:default_of_97_quality) do
|
193
|
-
Morandi.process('sample/sample.jpg', {}, 'sample/out-97.jpg')
|
194
|
-
File.size('sample/out-97.jpg')
|
195
|
-
end
|
196
|
-
|
197
|
-
let(:quality_of_40_by_options_args) do
|
198
|
-
Morandi.process('sample/sample.jpg', { 'quality' => 40 }, 'sample/out-40.jpg')
|
199
|
-
File.size('sample/out-40.jpg')
|
200
|
-
end
|
201
|
-
|
202
|
-
# Sort the output files' sizes and expect them to match to quality order
|
203
|
-
it 'creates files of increasing size' do
|
204
|
-
created_file_sizes = [default_of_97_quality, max_quality_file_size, quality_of_40_by_options_args].sort
|
205
|
-
expect(created_file_sizes).to eq([quality_of_40_by_options_args, default_of_97_quality, max_quality_file_size])
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
-
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
-
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
-
# loaded once.
|
5
|
-
#
|
6
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
-
|
8
|
-
require "pry"
|
9
|
-
|
10
|
-
RSpec.configure do |config|
|
11
|
-
config.run_all_when_everything_filtered = true
|
12
|
-
config.filter_run :focus
|
13
|
-
|
14
|
-
# Run specs in random order to surface order dependencies. If you find an
|
15
|
-
# order dependency and want to debug it, you can fix the order by providing
|
16
|
-
# the seed, which is printed after each run.
|
17
|
-
# --seed 1234
|
18
|
-
config.order = 'random'
|
19
|
-
end
|