image_vise 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- metadata +2 -36
- data/.gitignore +0 -8
- data/.travis.yml +0 -13
- data/DEVELOPMENT.md +0 -111
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -29
- data/README.md +0 -213
- data/Rakefile +0 -6
- data/SECURITY.md +0 -57
- data/examples/config.ru +0 -17
- data/examples/custom_image_operator.rb +0 -27
- data/examples/error_handline_appsignal.rb +0 -23
- data/examples/error_handling_sentry.rb +0 -25
- data/image_vise.gemspec +0 -43
- data/lib/image_vise/fetchers/fetcher_file.rb +0 -27
- data/lib/image_vise/fetchers/fetcher_http.rb +0 -42
- data/lib/image_vise/file_response.rb +0 -22
- data/lib/image_vise/image_request.rb +0 -70
- data/lib/image_vise/operators/auto_orient.rb +0 -10
- data/lib/image_vise/operators/background_fill.rb +0 -18
- data/lib/image_vise/operators/crop.rb +0 -32
- data/lib/image_vise/operators/ellipse_stencil.rb +0 -70
- data/lib/image_vise/operators/fit_crop.rb +0 -33
- data/lib/image_vise/operators/force_jpg_out.rb +0 -17
- data/lib/image_vise/operators/geom.rb +0 -16
- data/lib/image_vise/operators/sRGB_v4_ICC_preference_displayclass.icc +0 -0
- data/lib/image_vise/operators/sharpen.rb +0 -21
- data/lib/image_vise/operators/srgb.rb +0 -30
- data/lib/image_vise/operators/strip_metadata.rb +0 -10
- data/lib/image_vise/pipeline.rb +0 -64
- data/lib/image_vise/render_engine.rb +0 -298
- data/lib/image_vise/version.rb +0 -3
- data/lib/image_vise/writers/auto_writer.rb +0 -23
- data/lib/image_vise/writers/jpeg_writer.rb +0 -9
- data/lib/image_vise.rb +0 -177
@@ -1,27 +0,0 @@
|
|
1
|
-
class ImageVise::FetcherFile
|
2
|
-
class AccessError < StandardError
|
3
|
-
def http_status; 403; end
|
4
|
-
end
|
5
|
-
def self.fetch_uri_to_tempfile(uri)
|
6
|
-
tf = Tempfile.new 'imagevise-localfs-copy'
|
7
|
-
real_path_on_filesystem = File.expand_path(URI.decode(uri.path))
|
8
|
-
verify_filesystem_access! real_path_on_filesystem
|
9
|
-
# Do the checks
|
10
|
-
File.open(real_path_on_filesystem, 'rb') do |f|
|
11
|
-
IO.copy_stream(f, tf)
|
12
|
-
end
|
13
|
-
tf.rewind; tf
|
14
|
-
rescue Exception => e
|
15
|
-
ImageVise.close_and_unlink(tf)
|
16
|
-
raise e
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.verify_filesystem_access!(path_on_filesystem)
|
20
|
-
patterns = ImageVise.allowed_filesystem_sources
|
21
|
-
matches = patterns.any? { |glob_pattern| File.fnmatch?(glob_pattern, path_on_filesystem) }
|
22
|
-
raise AccessError, "filesystem access is disabled" unless patterns.any?
|
23
|
-
raise AccessError, "#{path_on_filesystem} is not on the path whitelist" unless matches
|
24
|
-
end
|
25
|
-
|
26
|
-
ImageVise.register_fetcher 'file', self
|
27
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
class ImageVise::FetcherHTTP
|
2
|
-
EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS = 5
|
3
|
-
|
4
|
-
class AccessError < StandardError; end
|
5
|
-
|
6
|
-
class UpstreamError < StandardError
|
7
|
-
attr_accessor :http_status
|
8
|
-
def initialize(http_status, message)
|
9
|
-
super(message)
|
10
|
-
@http_status = http_status
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.fetch_uri_to_tempfile(uri)
|
15
|
-
tf = Tempfile.new 'imagevise-http-download'
|
16
|
-
verify_uri_access!(uri)
|
17
|
-
s = Patron::Session.new
|
18
|
-
s.automatic_content_encoding = true
|
19
|
-
s.timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
|
20
|
-
s.connect_timeout = EXTERNAL_IMAGE_FETCH_TIMEOUT_SECONDS
|
21
|
-
|
22
|
-
response = s.get_file(uri.to_s, tf.path)
|
23
|
-
|
24
|
-
if response.status != 200
|
25
|
-
raise UpstreamError.new(response.status, "Unfortunate upstream response #{response.status} on #{uri}")
|
26
|
-
end
|
27
|
-
|
28
|
-
tf
|
29
|
-
rescue Exception => e
|
30
|
-
ImageVise.close_and_unlink(tf)
|
31
|
-
raise e
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.verify_uri_access!(uri)
|
35
|
-
host = uri.host
|
36
|
-
return if ImageVise.allowed_hosts.include?(uri.host)
|
37
|
-
raise AccessError, "#{uri} is not permitted as source"
|
38
|
-
end
|
39
|
-
|
40
|
-
ImageVise.register_fetcher 'http', self
|
41
|
-
ImageVise.register_fetcher 'https', self
|
42
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# Wrappers a given Tempfile for a Rack response.
|
2
|
-
# Will close _and_ unlink the Tempfile it contains.
|
3
|
-
class ImageVise::FileResponse
|
4
|
-
ONE_CHUNK_BYTES = 1024 * 1024 * 2
|
5
|
-
def initialize(file)
|
6
|
-
@file = file
|
7
|
-
end
|
8
|
-
|
9
|
-
def each
|
10
|
-
@file.flush # Make sure all the writes have been synchronized
|
11
|
-
# We can easily open another file descriptor
|
12
|
-
File.open(@file.path, 'rb') do |my_file_descriptor|
|
13
|
-
while data = my_file_descriptor.read(ONE_CHUNK_BYTES)
|
14
|
-
yield(data)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def close
|
20
|
-
ImageVise.close_and_unlink(@file)
|
21
|
-
end
|
22
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
require 'openssl'
|
2
|
-
|
3
|
-
class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
|
4
|
-
class InvalidRequest < ArgumentError; end
|
5
|
-
class SignatureError < InvalidRequest; end
|
6
|
-
class URLError < InvalidRequest; end
|
7
|
-
class MissingParameter < InvalidRequest; end
|
8
|
-
|
9
|
-
# Initializes a new ParamsChecker from given HTTP server framework
|
10
|
-
# params. The params can be symbol- or string-keyed, does not matter.
|
11
|
-
def self.from_params(qs_params:, secrets:)
|
12
|
-
base64_encoded_params = qs_params.fetch(:q) rescue qs_params.fetch('q')
|
13
|
-
given_signature = qs_params.fetch(:sig) rescue qs_params.fetch('sig')
|
14
|
-
|
15
|
-
# Unmask slashes and equals signs (if they are present)
|
16
|
-
base64_encoded_params = base64_encoded_params.tr('-', '/').tr('_', '+')
|
17
|
-
|
18
|
-
# Check the signature before decoding JSON (since we will be creating symbols)
|
19
|
-
unless valid_signature?(base64_encoded_params, given_signature, secrets)
|
20
|
-
raise SignatureError, "Invalid or missing signature"
|
21
|
-
end
|
22
|
-
|
23
|
-
# Decode the JSON
|
24
|
-
# (only AFTER the signature has been validated, so we can use symbol keys)
|
25
|
-
decoded_json = Base64.decode64(base64_encoded_params)
|
26
|
-
params = JSON.parse(decoded_json, symbolize_names: true)
|
27
|
-
|
28
|
-
# Pick up the URL and validate it
|
29
|
-
source_url_str = params.fetch(:src_url).to_s
|
30
|
-
raise URLError, "the :src_url parameter must be non-empty" if source_url_str.empty?
|
31
|
-
pipeline_definition = params.fetch(:pipeline)
|
32
|
-
new(src_url: URI(source_url_str), pipeline: ImageVise::Pipeline.from_param(pipeline_definition))
|
33
|
-
rescue KeyError => e
|
34
|
-
raise InvalidRequest.new(e.message)
|
35
|
-
end
|
36
|
-
|
37
|
-
def to_path_params(signed_with_secret)
|
38
|
-
qs = to_query_string_params(signed_with_secret)
|
39
|
-
q_masked = qs.fetch(:q).tr('/', '-').tr('+', '_')
|
40
|
-
'/%s/%s' % [q_masked, qs[:sig]]
|
41
|
-
end
|
42
|
-
|
43
|
-
def to_query_string_params(signed_with_secret)
|
44
|
-
payload = JSON.dump(to_h)
|
45
|
-
base64_enc = Base64.strict_encode64(payload).gsub(/\=+$/, '')
|
46
|
-
{q: base64_enc, sig: OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, signed_with_secret, base64_enc)}
|
47
|
-
end
|
48
|
-
|
49
|
-
def to_h
|
50
|
-
{pipeline: pipeline.to_params, src_url: src_url.to_s}
|
51
|
-
end
|
52
|
-
|
53
|
-
def cache_etag
|
54
|
-
Digest::SHA1.hexdigest(JSON.dump(to_h))
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def self.valid_signature?(for_payload, given_signature, secrets)
|
60
|
-
# Check the signature against every key that we have,
|
61
|
-
# since different apps might be using different keys
|
62
|
-
seen_valid_signature = false
|
63
|
-
secrets.each do | stored_secret |
|
64
|
-
expected_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, stored_secret, for_payload)
|
65
|
-
result_for_this_key = Rack::Utils.secure_compare(expected_signature, given_signature)
|
66
|
-
seen_valid_signature ||= result_for_this_key
|
67
|
-
end
|
68
|
-
seen_valid_signature
|
69
|
-
end
|
70
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# Applies ImageMagick auto_orient to the image, so that i.e. mobile photos
|
2
|
-
# can be oriented correctly. The operation is applied destructively (changes actual pixel data)
|
3
|
-
#
|
4
|
-
# The corresponding Pipeline method is `auto_orient`.
|
5
|
-
class ImageVise::AutoOrient
|
6
|
-
def apply!(magick_image)
|
7
|
-
magick_image.auto_orient!
|
8
|
-
end
|
9
|
-
ImageVise.add_operator 'auto_orient', self
|
10
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# Applies a background fill color.
|
2
|
-
# Can handle most 'word' colors and hex color codes but not RGB values.
|
3
|
-
#
|
4
|
-
# The corresponding Pipeline method is `background_fill`.
|
5
|
-
class ImageVise::BackgroundFill < Ks.strict(:color)
|
6
|
-
def initialize(*)
|
7
|
-
super
|
8
|
-
self.color = color.to_s
|
9
|
-
raise ArgumentError, "the :color parameter must be present and not empty" if self.color.empty?
|
10
|
-
end
|
11
|
-
|
12
|
-
def apply!(image)
|
13
|
-
image.border!(0, 0, color)
|
14
|
-
image.alpha(Magick::DeactivateAlphaChannel)
|
15
|
-
end
|
16
|
-
|
17
|
-
ImageVise.add_operator 'background_fill', self
|
18
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# Crops the image to the given dimensions with a given gravity. Gravities are shorthand versions
|
2
|
-
# of ImageMagick gravity parameters (see GRAVITY_PARAMS)
|
3
|
-
#
|
4
|
-
# The corresponding Pipeline method is `crop`.
|
5
|
-
class ImageVise::Crop < Ks.strict(:width, :height, :gravity)
|
6
|
-
GRAVITY_PARAMS = {
|
7
|
-
'nw' => Magick::NorthWestGravity,
|
8
|
-
'n' => Magick::NorthGravity,
|
9
|
-
'ne' => Magick::NorthEastGravity,
|
10
|
-
'w' => Magick::WestGravity,
|
11
|
-
'c' => Magick::CenterGravity,
|
12
|
-
'e' => Magick::EastGravity,
|
13
|
-
'sw' => Magick::SouthWestGravity,
|
14
|
-
's' => Magick::SouthGravity,
|
15
|
-
'se' => Magick::SouthEastGravity,
|
16
|
-
}
|
17
|
-
|
18
|
-
def initialize(*)
|
19
|
-
super
|
20
|
-
self.width = width.to_i
|
21
|
-
self.height = height.to_i
|
22
|
-
raise ArgumentError, ":width must positive" unless width > 0
|
23
|
-
raise ArgumentError, ":height must positive" unless height > 0
|
24
|
-
raise ArgumentError, ":gravity must be within the permitted values" unless GRAVITY_PARAMS.key? gravity
|
25
|
-
end
|
26
|
-
|
27
|
-
def apply!(image)
|
28
|
-
image.crop!(GRAVITY_PARAMS.fetch(gravity), width, height, remove_padding_data_outside_window = true)
|
29
|
-
end
|
30
|
-
|
31
|
-
ImageVise.add_operator 'crop', self
|
32
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# Applies an elliptic stencil around the entire image. The stencil will fit inside the image boundaries,
|
2
|
-
# with about 1 pixel cushion on each side to provide smooth anti-aliased edges. If the input image to be
|
3
|
-
# provessed is square, the ellipse will turn into a neat circle.
|
4
|
-
#
|
5
|
-
# This adds an alpha channel to the image being processed (and premultiplies the RGB channels by it). This
|
6
|
-
# will force the RenderEngine to return the processed image as a PNG in all cases, instead of keeping it
|
7
|
-
# in the original format.
|
8
|
-
#
|
9
|
-
# The corresponding Pipeline method is `ellipse_stencil`.
|
10
|
-
class ImageVise::EllipseStencil
|
11
|
-
C_black = 'black'.freeze
|
12
|
-
C_white = 'white'.freeze
|
13
|
-
private_constant :C_white, :C_black
|
14
|
-
|
15
|
-
def apply!(magick_image)
|
16
|
-
width, height = magick_image.columns, magick_image.rows
|
17
|
-
|
18
|
-
# This is a bit involved. We need to do a manual composite. Here is what it entails.
|
19
|
-
#
|
20
|
-
# Given a premultiplied RGB image B, and a grayscale mask A, we need to do the following
|
21
|
-
# operation:
|
22
|
-
#
|
23
|
-
# BrBgBb / Ba * (Ba * A)
|
24
|
-
#
|
25
|
-
# Since ImageMagick works with unpremultiplied alphas, it is doable - but special care
|
26
|
-
# must be taken not to overmult or overdivide.
|
27
|
-
#
|
28
|
-
# To begin,generate a black and white image for the stencil
|
29
|
-
mask = Magick::Image.new(width, height)
|
30
|
-
draw_circle(mask, width, height)
|
31
|
-
|
32
|
-
# At this stage the mask contains a B/W image of the circle, black outside, white inside.
|
33
|
-
# Retain the alpha of the original in a separate image
|
34
|
-
only_alpha = magick_image.copy
|
35
|
-
only_alpha.alpha(Magick::ExtractAlphaChannel)
|
36
|
-
mask.composite!(only_alpha, Magick::CenterGravity, Magick::MultiplyCompositeOp)
|
37
|
-
|
38
|
-
# With this composite op, enabling alpha on the destination image is
|
39
|
-
# not required - it will be enabled automatically.
|
40
|
-
# The CopyOpacityCompositeOp implies that we copy the grayscale version
|
41
|
-
# of the RGB channels as the alpha channel, so for some weird reason we need
|
42
|
-
# to disable the alpha on our mask image
|
43
|
-
mask.alpha(Magick::DeactivateAlphaChannel)
|
44
|
-
# And perform the operation (set gray(RGB) of mask as the A of magick_image)
|
45
|
-
magick_image.composite!(mask, Magick::CenterGravity, Magick::CopyOpacityCompositeOp)
|
46
|
-
ensure
|
47
|
-
[mask, only_alpha].each do |maybe_image|
|
48
|
-
ImageVise.destroy(maybe_image)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def draw_circle(into_image, width, height)
|
53
|
-
center_x = (width / 2.0)
|
54
|
-
center_y = (height / 2.0)
|
55
|
-
# Make sure all the edges are anti-aliased
|
56
|
-
radius_width = center_x - 1.5
|
57
|
-
radius_height = center_y - 1.5
|
58
|
-
|
59
|
-
gc = Magick::Draw.new
|
60
|
-
gc.fill C_black
|
61
|
-
gc.rectangle(0, 0, width, height)
|
62
|
-
gc.fill C_white
|
63
|
-
gc.ellipse(center_x, center_y, radius_width, radius_height, deg_start=0, deg_end=360)
|
64
|
-
gc.draw(into_image)
|
65
|
-
ensure
|
66
|
-
ImageVise.destroy(gc)
|
67
|
-
end
|
68
|
-
|
69
|
-
ImageVise.add_operator 'ellipse_stencil', self
|
70
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# Fits the image based on the smaller-side fit. This means that the image is going to be fit
|
2
|
-
# into the requested rectangle so that all of the pixels of the rectangle are filled. The
|
3
|
-
# gravity parameter defines the crop gravity (on corners, sides, or in the middle).
|
4
|
-
#
|
5
|
-
# The corresponding Pipeline method is `fit_crop`.
|
6
|
-
class ImageVise::FitCrop < Ks.strict(:width, :height, :gravity)
|
7
|
-
GRAVITY_PARAMS = {
|
8
|
-
'nw' => Magick::NorthWestGravity,
|
9
|
-
'n' => Magick::NorthGravity,
|
10
|
-
'ne' => Magick::NorthEastGravity,
|
11
|
-
'w' => Magick::WestGravity,
|
12
|
-
'c' => Magick::CenterGravity,
|
13
|
-
'e' => Magick::EastGravity,
|
14
|
-
'sw' => Magick::SouthWestGravity,
|
15
|
-
's' => Magick::SouthGravity,
|
16
|
-
'se' => Magick::SouthEastGravity,
|
17
|
-
}
|
18
|
-
|
19
|
-
def initialize(*)
|
20
|
-
super
|
21
|
-
self.width = width.to_i
|
22
|
-
self.height = height.to_i
|
23
|
-
raise ArgumentError, ":width must positive" unless width > 0
|
24
|
-
raise ArgumentError, ":height must positive" unless height > 0
|
25
|
-
raise ArgumentError, ":gravity must be within the permitted values" unless GRAVITY_PARAMS.key? gravity
|
26
|
-
end
|
27
|
-
|
28
|
-
def apply!(magick_image)
|
29
|
-
magick_image.resize_to_fill! width, height, GRAVITY_PARAMS.fetch(gravity)
|
30
|
-
end
|
31
|
-
|
32
|
-
ImageVise.add_operator 'fit_crop', self
|
33
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# Forces the output format to be JPEG and specifies the quality factor to use when saving
|
2
|
-
#
|
3
|
-
# The corresponding Pipeline method is `force_jpg_out`.
|
4
|
-
class ImageVise::ForceJPGOut < Ks.strict(:quality)
|
5
|
-
def initialize(quality:)
|
6
|
-
unless (0..100).cover?(quality)
|
7
|
-
raise ArgumentError, "the :quality setting must be within 0..100, but was %d" % quality
|
8
|
-
end
|
9
|
-
self.quality = quality
|
10
|
-
end
|
11
|
-
|
12
|
-
def apply!(_, metadata)
|
13
|
-
metadata[:writer] = ImageVise::JPGWriter.new(quality: quality)
|
14
|
-
end
|
15
|
-
|
16
|
-
ImageVise.add_operator 'force_jpg_out', self
|
17
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# Applies a transformation using an ImageMagick geometry string
|
2
|
-
#
|
3
|
-
# The corresponding Pipeline method is `geom`.
|
4
|
-
class ImageVise::Geom < Ks.strict(:geometry_string)
|
5
|
-
def initialize(*)
|
6
|
-
super
|
7
|
-
self.geometry_string = geometry_string.to_s
|
8
|
-
raise ArgumentError, "the :geom parameter must be present and not empty" if self.geometry_string.empty?
|
9
|
-
end
|
10
|
-
|
11
|
-
def apply!(image)
|
12
|
-
image.change_geometry(geometry_string) { |cols, rows, _| image.resize!(cols,rows) }
|
13
|
-
end
|
14
|
-
|
15
|
-
ImageVise.add_operator 'geom', self
|
16
|
-
end
|
Binary file
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# Applies a sharpening filter to the image.
|
2
|
-
#
|
3
|
-
# The corresponding Pipeline method is `sharpen`.
|
4
|
-
class ImageVise::Sharpen < Ks.strict(:radius, :sigma)
|
5
|
-
def initialize(*)
|
6
|
-
super
|
7
|
-
self.radius = radius.to_f
|
8
|
-
self.sigma = sigma.to_f
|
9
|
-
raise ArgumentError, ":radius must positive" unless sigma > 0
|
10
|
-
raise ArgumentError, ":sigma must positive" unless sigma > 0
|
11
|
-
end
|
12
|
-
|
13
|
-
def apply!(magick_image)
|
14
|
-
sharpened_image = magick_image.sharpen(radius, sigma)
|
15
|
-
magick_image.composite!(sharpened_image, Magick::CenterGravity, Magick::CopyCompositeOp)
|
16
|
-
ensure
|
17
|
-
ImageVise.destroy(sharpened_image)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
ImageVise.add_operator 'sharpen', ImageVise::Sharpen
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# Applies the sRGB profile to the image.
|
2
|
-
# For this to work, your ImageMagick must be built
|
3
|
-
# witl LCMS support. On OSX, you need to use the brew install
|
4
|
-
# command with the following options:
|
5
|
-
#
|
6
|
-
# $brew install imagemagick --with-little-cms --with-little-cms2
|
7
|
-
#
|
8
|
-
# You can verify if you do have LittleCMS support by checking the
|
9
|
-
# delegates list that `$convert --version` outputs:
|
10
|
-
#
|
11
|
-
# For instance, if you do not have it, the list will look like this:
|
12
|
-
#
|
13
|
-
# $ convert --version
|
14
|
-
# ...
|
15
|
-
# Delegates (built-in): bzlib freetype jng jpeg ltdl lzma png tiff xml zlib
|
16
|
-
#
|
17
|
-
# whereas if you do, the list will include the "lcms" delegate:
|
18
|
-
#
|
19
|
-
# $ convert --version
|
20
|
-
# ...
|
21
|
-
# Delegates (built-in): bzlib freetype jng jpeg lcms ltdl lzma png tiff xml zlib
|
22
|
-
#
|
23
|
-
# The corresponding Pipeline method is `srgb`.
|
24
|
-
class ImageVise::SRGB
|
25
|
-
PROFILE_PATH = File.expand_path(__dir__ + '/sRGB_v4_ICC_preference_displayclass.icc')
|
26
|
-
def apply!(magick_image)
|
27
|
-
magick_image.add_profile(PROFILE_PATH)
|
28
|
-
end
|
29
|
-
ImageVise.add_operator 'srgb', self
|
30
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# Strips metadata from the image (EXIF, IPTC etc.) using the
|
2
|
-
# RMagick `strip!` method
|
3
|
-
#
|
4
|
-
# The corresponding Pipeline method is `strip_metadata`.
|
5
|
-
class ImageVise::StripMetadata
|
6
|
-
def apply!(magick_image)
|
7
|
-
magick_image.strip!
|
8
|
-
end
|
9
|
-
ImageVise.add_operator 'strip_metadata', self
|
10
|
-
end
|
data/lib/image_vise/pipeline.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
class ImageVise::Pipeline
|
2
|
-
def self.operator_by_name(name)
|
3
|
-
operator = ImageVise.operator_from(name) or raise "Unknown operator #{name}"
|
4
|
-
end
|
5
|
-
|
6
|
-
def self.from_param(array_of_operator_names_to_operator_params)
|
7
|
-
operators = array_of_operator_names_to_operator_params.map do |(operator_name, operator_params)|
|
8
|
-
operator_class = operator_by_name(operator_name)
|
9
|
-
if operator_params && operator_params.any? && operator_class.method(:new).arity.nonzero?
|
10
|
-
operator_class.new(**operator_params)
|
11
|
-
else
|
12
|
-
operator_class.new
|
13
|
-
end
|
14
|
-
end
|
15
|
-
new(operators)
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize(operators = [])
|
19
|
-
@ops = operators.to_a
|
20
|
-
end
|
21
|
-
|
22
|
-
def <<(image_operator)
|
23
|
-
@ops << image_operator; self
|
24
|
-
end
|
25
|
-
|
26
|
-
def empty?
|
27
|
-
@ops.empty?
|
28
|
-
end
|
29
|
-
|
30
|
-
def method_missing(method_name, *a, &blk)
|
31
|
-
operator_builder = ImageVise.operator_from(method_name)
|
32
|
-
self << operator_builder.new(*a)
|
33
|
-
end
|
34
|
-
|
35
|
-
def respond_to_missing?(method_name, *a)
|
36
|
-
ImageVise.defined_operators.include?(method_name.to_s)
|
37
|
-
end
|
38
|
-
|
39
|
-
def to_params
|
40
|
-
@ops.map do |operator|
|
41
|
-
operator_name = ImageVise.operator_name_for(operator)
|
42
|
-
operator_params = operator.respond_to?(:to_h) ? operator.to_h : {}
|
43
|
-
[operator_name, operator_params]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def apply!(magick_image, image_metadata)
|
48
|
-
@ops.each do |operator|
|
49
|
-
apply_operator_passing_metadata(magick_image, operator, image_metadata)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def apply_operator_passing_metadata(magick_image, operator, image_metadata)
|
54
|
-
if operator.method(:apply!).arity == 1
|
55
|
-
operator.apply!(magick_image)
|
56
|
-
else
|
57
|
-
operator.apply!(magick_image, image_metadata)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def each(&b)
|
62
|
-
@ops.each(&b)
|
63
|
-
end
|
64
|
-
end
|