image_vise 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.travis.yml +13 -0
  4. data/DEVELOPMENT.md +111 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +29 -0
  7. data/README.md +213 -0
  8. data/Rakefile +6 -0
  9. data/SECURITY.md +57 -0
  10. data/examples/config.ru +17 -0
  11. data/examples/custom_image_operator.rb +27 -0
  12. data/examples/error_handline_appsignal.rb +23 -0
  13. data/examples/error_handling_sentry.rb +25 -0
  14. data/image_vise.gemspec +43 -0
  15. data/lib/image_vise/fetchers/fetcher_file.rb +27 -0
  16. data/lib/image_vise/fetchers/fetcher_http.rb +42 -0
  17. data/lib/image_vise/file_response.rb +22 -0
  18. data/lib/image_vise/image_request.rb +70 -0
  19. data/lib/image_vise/operators/auto_orient.rb +10 -0
  20. data/lib/image_vise/operators/background_fill.rb +18 -0
  21. data/lib/image_vise/operators/crop.rb +32 -0
  22. data/lib/image_vise/operators/ellipse_stencil.rb +70 -0
  23. data/lib/image_vise/operators/fit_crop.rb +33 -0
  24. data/lib/image_vise/operators/force_jpg_out.rb +17 -0
  25. data/lib/image_vise/operators/geom.rb +16 -0
  26. data/lib/image_vise/operators/sRGB_v4_ICC_preference_displayclass.icc +0 -0
  27. data/lib/image_vise/operators/sharpen.rb +21 -0
  28. data/lib/image_vise/operators/srgb.rb +30 -0
  29. data/lib/image_vise/operators/strip_metadata.rb +10 -0
  30. data/lib/image_vise/pipeline.rb +64 -0
  31. data/lib/image_vise/render_engine.rb +298 -0
  32. data/lib/image_vise/version.rb +3 -0
  33. data/lib/image_vise/writers/auto_writer.rb +23 -0
  34. data/lib/image_vise/writers/jpeg_writer.rb +9 -0
  35. data/lib/image_vise.rb +177 -0
  36. data/spec/image_vise/auto_orient_spec.rb +10 -0
  37. data/spec/image_vise/background_fill_spec.rb +39 -0
  38. data/spec/image_vise/crop_spec.rb +20 -0
  39. data/spec/image_vise/ellipse_stencil_spec.rb +18 -0
  40. data/spec/image_vise/fetcher_file_spec.rb +48 -0
  41. data/spec/image_vise/fetcher_http_spec.rb +44 -0
  42. data/spec/image_vise/file_response_spec.rb +45 -0
  43. data/spec/image_vise/fit_crop_spec.rb +20 -0
  44. data/spec/image_vise/force_jpg_out_spec.rb +36 -0
  45. data/spec/image_vise/geom_spec.rb +33 -0
  46. data/spec/image_vise/image_request_spec.rb +62 -0
  47. data/spec/image_vise/pipeline_spec.rb +72 -0
  48. data/spec/image_vise/render_engine_spec.rb +336 -0
  49. data/spec/image_vise/sharpen_spec.rb +17 -0
  50. data/spec/image_vise/srgb_spec.rb +23 -0
  51. data/spec/image_vise/strip_metadata_spec.rb +14 -0
  52. data/spec/image_vise/writers/auto_writer_spec.rb +25 -0
  53. data/spec/image_vise/writers/jpeg_writer_spec.rb +32 -0
  54. data/spec/image_vise_spec.rb +110 -0
  55. data/spec/layers-with-blending.psd +0 -0
  56. data/spec/spec_helper.rb +112 -0
  57. data/spec/test_server.rb +61 -0
  58. data/spec/waterside_magic_hour.jpg +0 -0
  59. data/spec/waterside_magic_hour.psd +0 -0
  60. data/spec/waterside_magic_hour_adobergb.jpg +0 -0
  61. data/spec/waterside_magic_hour_gray.tif +0 -0
  62. data/spec/waterside_magic_hour_transp.png +0 -0
  63. metadata +63 -2
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe ImageVise::SRGB do
4
+ it 'applies the profile, creating a perceptible difference with the original' do
5
+ # This test will function only if you have RMagick with LCMS2 support
6
+ # built-in. If you do, the two images will look _very_ much like one
7
+ # another.
8
+ #
9
+ # If you don't, the images will look remarkably different
10
+ # (the AdobeRGB version has color values that match AdobeRGB
11
+ # primaries, and will render diffrently in pretty much any
12
+ # viewer).
13
+ image = Magick::Image.read(test_image_adobergb_path).first
14
+ subject.apply!(image)
15
+ image.strip!
16
+ examine_image(image, "from-adobergb-SHOULD-LOOK-IDENTICAL")
17
+
18
+ image = Magick::Image.read(test_image_path).first
19
+ subject.apply!(image)
20
+ image.strip!
21
+ examine_image(image, "from-srgb-SHOULD-LOOK-IDENTICAL")
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe ImageVise::StripMetadata do
4
+ it 'applies the strip! method to the image' do
5
+ image = Magick::Image.read(test_image_path).first
6
+ expect(image).to receive(:strip!).and_call_original
7
+ described_class.new.apply!(image)
8
+ end
9
+
10
+ it 'is registered with the operator registry' do
11
+ op = ImageVise.operator_from('strip_metadata')
12
+ expect(op).to eq(described_class)
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe ImageVise::AutoWriter do
4
+ it 'writes out a file with alpha as a PNG' do
5
+ sample_image = Magick::Image.read(test_image_png_transparency).first
6
+ tf = Tempfile.new 'outt'
7
+ subject.write_image!(sample_image, metadata=nil, tf.path)
8
+
9
+ tf.rewind
10
+ expect(tf.size).not_to be_zero
11
+ fmt = MagicBytes.read_and_detect(tf)
12
+ expect(fmt.ext).to eq('png')
13
+ end
14
+
15
+ it 'writes out a file without alpha as JPEG' do
16
+ sample_image = Magick::Image.read(test_image_path_tif).first
17
+ tf = Tempfile.new 'outt'
18
+ subject.write_image!(sample_image, metadata=nil, tf.path)
19
+
20
+ tf.rewind
21
+ expect(tf.size).not_to be_zero
22
+ fmt = MagicBytes.read_and_detect(tf)
23
+ expect(fmt.ext).to eq('jpg')
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe ImageVise::JPGWriter do
4
+ it 'writes out a file with alpha as a JPEG regardless' do
5
+ sample_image = Magick::Image.read(test_image_png_transparency).first
6
+ tf = Tempfile.new 'outt'
7
+
8
+ described_class.new(quality: 75).write_image!(sample_image, metadata=nil, tf.path)
9
+
10
+ tf.rewind
11
+ expect(tf.size).not_to be_zero
12
+ fmt = MagicBytes.read_and_detect(tf)
13
+ expect(fmt.ext).to eq('jpg')
14
+ end
15
+
16
+ it 'honors the JPEG compression quality setting, producing a smaller file with smaller quality' do
17
+ sample_image = Magick::Image.read(test_image_path_tif).first
18
+
19
+ tf_hi = Tempfile.new 'hi'
20
+ tf_lo = Tempfile.new 'lo'
21
+
22
+ described_class.new(quality: 50).write_image!(sample_image, metadata=nil, tf_hi.path)
23
+ described_class.new(quality: 10).write_image!(sample_image, metadata=nil, tf_lo.path)
24
+
25
+ tf_hi.rewind
26
+ tf_lo.rewind
27
+
28
+ expect(tf_hi.size).not_to be_zero
29
+ expect(tf_lo.size).not_to be_zero
30
+ expect(tf_lo.size).to be < tf_hi.size
31
+ end
32
+ end
@@ -0,0 +1,110 @@
1
+ require_relative 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ describe ImageVise do
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ described_class.new
9
+ end
10
+
11
+ context 'ImageVise.allowed_hosts' do
12
+ it 'returns the allowed hosts and is empty by default' do
13
+ expect(described_class.allowed_hosts).to be_empty
14
+ end
15
+
16
+ it 'allows add_allowed_host! and reset_allowed_hosts!' do
17
+ described_class.add_allowed_host!('www.imageboard.im')
18
+ expect(described_class.allowed_hosts).to include('www.imageboard.im')
19
+ described_class.reset_allowed_hosts!
20
+ expect(described_class.allowed_hosts).not_to include('www.imageboard.im')
21
+ end
22
+ end
23
+
24
+ context 'ImageVise.secret_keys' do
25
+ it 'raises when asked for a key and no keys has been set' do
26
+ expect {
27
+ described_class.secret_keys
28
+ }.to raise_error("No keys set, add a key using `ImageVise.add_secret_key!(key)'")
29
+ end
30
+
31
+ it 'allows add_secret_key!(key) and reset_secret_keys!' do
32
+ described_class.add_secret_key!('l33t')
33
+ expect(described_class.secret_keys).to include('l33t')
34
+ described_class.reset_secret_keys!
35
+ expect {
36
+ expect(described_class.secret_keys)
37
+ }.to raise_error(/add a key using/)
38
+ end
39
+ end
40
+
41
+ describe 'ImageVise.new.call' do
42
+ it 'instantiates a new app and performs call() on it' do
43
+ expect_any_instance_of(ImageVise::RenderEngine).to receive(:call).with(:mock_env) { :yes }
44
+ ImageVise.new.call(:mock_env)
45
+ end
46
+ end
47
+
48
+ describe 'ImageVise.call' do
49
+ it 'instantiates a new app and performs call() on it' do
50
+ expect_any_instance_of(ImageVise::RenderEngine).to receive(:call).with(:mock_env) { :yes }
51
+ ImageVise.call(:mock_env)
52
+ end
53
+ end
54
+
55
+ describe '.image_params' do
56
+ it 'generates a Hash with paremeters for processing the resized image' do
57
+ params = ImageVise.image_params(src_url: 'http://host.com/image.jpg', secret: 'l33t') do |pipe|
58
+ pipe.fit_crop width: 128, height: 256, gravity: 'c'
59
+ end
60
+ expect(params).to be_kind_of(Hash)
61
+ expect(params[:q]).not_to be_empty
62
+ expect(params[:sig]).not_to be_empty
63
+ end
64
+ end
65
+
66
+ describe 'methods dealing with fetchers' do
67
+ it 'returns the fetchers for the default schemes' do
68
+ http = ImageVise.fetcher_for('http')
69
+ expect(http).to respond_to(:fetch_uri_to_tempfile)
70
+ file = ImageVise.fetcher_for('file')
71
+ expect(http).to respond_to(:fetch_uri_to_tempfile)
72
+
73
+ expect {
74
+ ImageVise.fetcher_for('undernet')
75
+ }.to raise_error(/No fetcher registered/)
76
+ end
77
+ end
78
+
79
+ describe '.image_path' do
80
+ it 'returns the path to the image within the application' do
81
+ path = ImageVise.image_path(src_url: 'file://tmp/img.jpg', secret: 'a') do |p|
82
+ p.ellipse_stencil
83
+ end
84
+ expect(path).to start_with('/')
85
+ end
86
+ end
87
+
88
+ describe 'methods dealing with the operator list' do
89
+ it 'have the basic operators already set up' do
90
+ oplist = ImageVise.defined_operator_names
91
+ expect(oplist).to include('sharpen')
92
+ expect(oplist).to include('crop')
93
+ end
94
+
95
+ it 'allows an operator to be added and retrieved' do
96
+ class CustomOp; end
97
+ ImageVise.add_operator 'custom_op', CustomOp
98
+ expect(ImageVise.operator_from(:custom_op)).to eq(CustomOp)
99
+ expect(ImageVise.operator_name_for(CustomOp.new)).to eq('custom_op')
100
+ expect(ImageVise.defined_operator_names).to include('custom_op')
101
+ end
102
+
103
+ it 'raises an exception when an operator key is requested that does not exist' do
104
+ class UnknownOp; end
105
+ expect {
106
+ ImageVise.operator_name_for(UnknownOp.new)
107
+ }.to raise_error(/not registered using ImageVise/)
108
+ end
109
+ end
110
+ end
Binary file
@@ -0,0 +1,112 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require 'tmpdir'
5
+ require 'securerandom'
6
+
7
+ require 'addressable/uri'
8
+ require 'strenv'
9
+ require_relative 'test_server'
10
+
11
+ TEST_RENDERS_DIR = Dir.mktmpdir
12
+
13
+ module Examine
14
+ def examine_image(magick_image, name_tag = 'test-img')
15
+ # When doing TDD, waiting for stuff to open is a drag - allow
16
+ # it to be squelched using 2 envvars. Also viewing images
17
+ # makes no sense on CI unless we bother with artifacts.
18
+ # The first one is what Gitlab-CI sets for us.
19
+ return if ENV.key?("CI_BUILD_ID")
20
+ return if ENV.key?("SKIP_INTERACTIVE")
21
+
22
+ Dir.mkdir(TEST_RENDERS_DIR) unless File.exist?(TEST_RENDERS_DIR)
23
+ path = File.join(TEST_RENDERS_DIR, name_tag + '.png')
24
+ magick_image.format = 'png'
25
+ magick_image.write(path)
26
+ `open #{path}`
27
+ end
28
+
29
+ def examine_image_from_string(string)
30
+ # When doing TDD, waiting for stuff to open is a drag - allow
31
+ # it to be squelched using 2 envvars. Also viewing images
32
+ # makes no sense on CI unless we bother with artifacts.
33
+ # The first one is what Gitlab-CI sets for us.
34
+ return if ENV.key?("CI_BUILD_ID")
35
+ return if ENV.key?("SKIP_INTERACTIVE")
36
+
37
+ Dir.mkdir(TEST_RENDERS_DIR) unless File.exist?(TEST_RENDERS_DIR)
38
+ random_name = 'test-image-%s' % SecureRandom.hex(3)
39
+ path = File.join(TEST_RENDERS_DIR, random_name)
40
+ File.open(path, 'wb'){|f| f << string }
41
+ `open #{path}`
42
+ end
43
+ end
44
+
45
+ require 'simplecov'
46
+ SimpleCov.start do
47
+ add_filter "/spec/"
48
+ end
49
+
50
+ require_relative '../lib/image_vise'
51
+
52
+ RSpec.configure do | config |
53
+ config.order = 'random'
54
+ config.include Examine
55
+ config.before :suite do
56
+ TestServer.start(nil, ssl=false, port=9001)
57
+ end
58
+
59
+ config.after :each do
60
+ ImageVise.reset_allowed_hosts!
61
+ ImageVise.reset_secret_keys!
62
+ end
63
+
64
+ config.after :suite do
65
+ sleep 2
66
+ FileUtils.rm_rf(TEST_RENDERS_DIR)
67
+ end
68
+
69
+ def test_image_path
70
+ File.expand_path(__dir__ + '/waterside_magic_hour.jpg')
71
+ end
72
+
73
+ def test_image_path_psd
74
+ File.expand_path(__dir__ + '/waterside_magic_hour.psd')
75
+ end
76
+
77
+ def test_image_path_tif
78
+ File.expand_path(__dir__ + '/waterside_magic_hour_gray.tif')
79
+ end
80
+
81
+ def test_image_adobergb_path
82
+ File.expand_path(__dir__ + '/waterside_magic_hour_adobergb.jpg')
83
+ end
84
+
85
+ def test_image_png_transparency
86
+ File.expand_path(__dir__ + '/waterside_magic_hour_transp.png')
87
+ end
88
+
89
+ def public_url
90
+ 'http://localhost:9001/waterside_magic_hour.jpg'
91
+ end
92
+
93
+ def public_url_psd
94
+ 'http://localhost:9001/waterside_magic_hour.psd'
95
+ end
96
+
97
+ def public_url_psd_multilayer
98
+ 'http://localhost:9001/layers-with-blending.psd'
99
+ end
100
+
101
+ def public_url_tif
102
+ 'http://localhost:9001/waterside_magic_hour_gray.tif'
103
+ end
104
+
105
+ def public_url_png_transparency
106
+ 'http://localhost:9001/waterside_magic_hour_transp.png'
107
+ end
108
+
109
+ config.around :each do |e|
110
+ STRICT_ENV.with_protected_env { e.run }
111
+ end
112
+ end
@@ -0,0 +1,61 @@
1
+ require 'webrick'
2
+ include WEBrick
3
+
4
+ class ForbiddenServlet < HTTPServlet::AbstractServlet
5
+ def do_GET(req,res)
6
+ res['Content-Type'] = "text/plain"
7
+ res.status = 403
8
+ end
9
+ end
10
+
11
+ class TestServer
12
+ def self.start( log_file = nil, ssl = false, port = 9001 )
13
+ new(log_file, ssl, port).start
14
+ end
15
+
16
+ def initialize( log_file = nil, ssl = false, port = 9001 )
17
+ log_file ||= StringIO.new
18
+ log = WEBrick::Log.new(log_file)
19
+
20
+ options = {
21
+ :Port => port,
22
+ :Logger => log,
23
+ :AccessLog => [
24
+ [ log, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
25
+ [ log, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
26
+ ],
27
+ :DocumentRoot => File.expand_path(__dir__),
28
+ }
29
+
30
+ if ssl
31
+ options[:SSLEnable] = true
32
+ options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.open("spec/certs/cacert.pem").read)
33
+ options[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.open("spec/certs/privkey.pem").read)
34
+ options[:SSLCertName] = [ ["CN", WEBrick::Utils::getservername ] ]
35
+ end
36
+
37
+ @server = WEBrick::HTTPServer.new(options)
38
+ @server.mount("/forbidden", ForbiddenServlet)
39
+ end
40
+
41
+ def start
42
+ trap('INT') {
43
+ begin
44
+ @server.shutdown unless @server.nil?
45
+ rescue Object => e
46
+ $stderr.puts "Error #{__FILE__}:#{__LINE__}\n#{e.message}"
47
+ end
48
+ }
49
+
50
+ @thread = Thread.new { @server.start }
51
+ Thread.pass
52
+ self
53
+ end
54
+
55
+ def join
56
+ if defined? @thread and @thread
57
+ @thread.join
58
+ end
59
+ self
60
+ end
61
+ end
Binary file
Binary file
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_vise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -184,7 +184,68 @@ email:
184
184
  executables: []
185
185
  extensions: []
186
186
  extra_rdoc_files: []
187
- files: []
187
+ files:
188
+ - ".gitignore"
189
+ - ".travis.yml"
190
+ - DEVELOPMENT.md
191
+ - Gemfile
192
+ - LICENSE.txt
193
+ - README.md
194
+ - Rakefile
195
+ - SECURITY.md
196
+ - examples/config.ru
197
+ - examples/custom_image_operator.rb
198
+ - examples/error_handline_appsignal.rb
199
+ - examples/error_handling_sentry.rb
200
+ - image_vise.gemspec
201
+ - lib/image_vise.rb
202
+ - lib/image_vise/fetchers/fetcher_file.rb
203
+ - lib/image_vise/fetchers/fetcher_http.rb
204
+ - lib/image_vise/file_response.rb
205
+ - lib/image_vise/image_request.rb
206
+ - lib/image_vise/operators/auto_orient.rb
207
+ - lib/image_vise/operators/background_fill.rb
208
+ - lib/image_vise/operators/crop.rb
209
+ - lib/image_vise/operators/ellipse_stencil.rb
210
+ - lib/image_vise/operators/fit_crop.rb
211
+ - lib/image_vise/operators/force_jpg_out.rb
212
+ - lib/image_vise/operators/geom.rb
213
+ - lib/image_vise/operators/sRGB_v4_ICC_preference_displayclass.icc
214
+ - lib/image_vise/operators/sharpen.rb
215
+ - lib/image_vise/operators/srgb.rb
216
+ - lib/image_vise/operators/strip_metadata.rb
217
+ - lib/image_vise/pipeline.rb
218
+ - lib/image_vise/render_engine.rb
219
+ - lib/image_vise/version.rb
220
+ - lib/image_vise/writers/auto_writer.rb
221
+ - lib/image_vise/writers/jpeg_writer.rb
222
+ - spec/image_vise/auto_orient_spec.rb
223
+ - spec/image_vise/background_fill_spec.rb
224
+ - spec/image_vise/crop_spec.rb
225
+ - spec/image_vise/ellipse_stencil_spec.rb
226
+ - spec/image_vise/fetcher_file_spec.rb
227
+ - spec/image_vise/fetcher_http_spec.rb
228
+ - spec/image_vise/file_response_spec.rb
229
+ - spec/image_vise/fit_crop_spec.rb
230
+ - spec/image_vise/force_jpg_out_spec.rb
231
+ - spec/image_vise/geom_spec.rb
232
+ - spec/image_vise/image_request_spec.rb
233
+ - spec/image_vise/pipeline_spec.rb
234
+ - spec/image_vise/render_engine_spec.rb
235
+ - spec/image_vise/sharpen_spec.rb
236
+ - spec/image_vise/srgb_spec.rb
237
+ - spec/image_vise/strip_metadata_spec.rb
238
+ - spec/image_vise/writers/auto_writer_spec.rb
239
+ - spec/image_vise/writers/jpeg_writer_spec.rb
240
+ - spec/image_vise_spec.rb
241
+ - spec/layers-with-blending.psd
242
+ - spec/spec_helper.rb
243
+ - spec/test_server.rb
244
+ - spec/waterside_magic_hour.jpg
245
+ - spec/waterside_magic_hour.psd
246
+ - spec/waterside_magic_hour_adobergb.jpg
247
+ - spec/waterside_magic_hour_gray.tif
248
+ - spec/waterside_magic_hour_transp.png
188
249
  homepage: https://github.com/WeTransfer/image_vise
189
250
  licenses:
190
251
  - MIT