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