lib/image_processing/pipeline.rb in image_processing-0.11.1 vs lib/image_processing/pipeline.rb in image_processing-0.11.2
- old
+ new
@@ -1,59 +1,92 @@
require "tempfile"
module ImageProcessing
class Pipeline
- include Chainable
+ DEFAULT_FORMAT = "jpg"
+ attr_reader :source, :loader, :saver, :format, :operations, :processor_class, :destination
+
def initialize(options)
- @default_options = options
+ options.each do |name, value|
+ value = normalize_source(value, options) if name == :source
+ instance_variable_set(:"@#{name}", value)
+ end
end
- def call!(save: true, destination: nil)
- fail Error, "source file is not provided" unless default_options[:source]
+ def call(save: true)
+ processor = processor_class.new(self)
+ image = processor.load_image(source, **loader)
- image_class = default_options[:processor]::IMAGE_CLASS
+ operations.each do |name, args|
+ image = processor.apply_operation(name, image, *args)
+ end
- if default_options[:source].is_a?(image_class)
- source = default_options[:source]
- elsif default_options[:source].is_a?(String)
- source = default_options[:source]
- elsif default_options[:source].respond_to?(:path)
- source = default_options[:source].path
- elsif default_options[:source].respond_to?(:to_path)
- source = default_options[:source].to_path
+ if save == false
+ image
+ elsif destination
+ handle_destination do
+ processor.save_image(image, destination, **saver)
+ end
else
- fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{image_class} object"
+ create_tempfile do |tempfile|
+ processor.save_image(image, tempfile.path, **saver)
+ end
end
+ end
- processor = default_options[:processor].new
- image = processor.load_image(source, default_options[:loader])
+ def source_path
+ source if source.is_a?(String)
+ end
- default_options[:operations].each do |name, args|
- if name == :custom
- image = args.first.call(image) || image
- else
- image = processor.apply_operation(name, image, *args)
- end
- end
+ def destination_format
+ format = File.extname(destination)[1..-1] if destination
+ format ||= self.format
+ format ||= File.extname(source_path)[1..-1] if source_path
- return image unless save
+ format || DEFAULT_FORMAT
+ end
- return processor.save_image(image, destination, default_options[:saver]) if destination
+ private
- source_path = source if source.is_a?(String)
- format = default_options[:format] || File.extname(source_path.to_s)[1..-1] || "jpg"
+ def create_tempfile
+ tempfile = Tempfile.new(["image_processing", ".#{destination_format}"], binmode: true)
- result = Tempfile.new(["image_processing", ".#{format}"], binmode: true)
+ yield tempfile
- begin
- processor.save_image(image, result.path, default_options[:saver])
- rescue
- result.close!
- raise
- end
+ tempfile.open
+ tempfile
+ rescue
+ tempfile.close! if tempfile
+ raise
+ end
- result.open
- result
+ # In case of processing errors, both libvips and imagemagick will leave the
+ # empty destination file they created, so this method makes sure it is
+ # deleted in case an exception is raised on saving the image.
+ def handle_destination
+ destination_existed = File.exist?(destination)
+ yield
+ rescue
+ File.delete(destination) if File.exist?(destination) && !destination_existed
+ raise
+ end
+
+ def normalize_source(source, options)
+ fail Error, "source file is not provided" unless source
+
+ image_class = options[:processor_class]::IMAGE_CLASS
+
+ if source.is_a?(image_class)
+ source
+ elsif source.is_a?(String)
+ source
+ elsif source.respond_to?(:path)
+ source.path
+ elsif source.respond_to?(:to_path)
+ source.to_path
+ else
+ fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{image_class} object"
+ end
end
end
end