class ImageProcessing::Vips::Processor
def self.load_image(path_or_image, loader: nil, autorot: true, **options)
loader-specific options (e.g. interlacing). Afterwards auto-rotates the
Loads the image on disk into a Vips::Image object. Accepts additional
def self.load_image(path_or_image, loader: nil, autorot: true, **options) if path_or_image.is_a?(::Vips::Image) image = path_or_image else path = path_or_image if loader image = ::Vips::Image.public_send(:"#{loader}load", path, **options) else options = Utils.select_valid_loader_options(path, options) image = ::Vips::Image.new_from_file(path, **options) end end image = image.autorot if autorot && !options.key?(:autorotate) image end
def self.save_image(image, path, saver: nil, quality: nil, **options)
pipeline defined in the Vips::Image object. Accepts additional
Writes the Vips::Image object to disk. This starts the processing
def self.save_image(image, path, saver: nil, quality: nil, **options) options[:Q] = quality if quality if saver image.public_send(:"#{saver}save", path, **options) else options = Utils.select_valid_saver_options(path, options) image.write_to_file(path, **options) end end
def self.supports_resize_on_load?
def self.supports_resize_on_load? true end
def composite(overlay, _mode = nil, mode: "over", gravity: "north-west", offset: nil, **options)
Overlays the specified image over the current one. Supports specifying
def composite(overlay, _mode = nil, mode: "over", gravity: "north-west", offset: nil, **options) # if the mode argument is given, call the original Vips::Image#composite if _mode overlay = [overlay] unless overlay.is_a?(Array) overlay = overlay.map { |object| convert_to_image(object, "overlay") } return image.composite(overlay, _mode, **options) end overlay = convert_to_image(overlay, "overlay") # add alpha channel so that #gravity can use a transparent background overlay = overlay.add_alpha unless overlay.has_alpha? # apply offset with correct gravity and make remainder transparent if offset opposite_gravity = gravity.to_s.gsub(/\w+/, "north"=>"south", "south"=>"north", "east"=>"west", "west"=>"east") overlay = overlay.gravity(opposite_gravity, overlay.width + offset.first, overlay.height + offset.last) end # create image-sized transparent background and apply specified gravity overlay = overlay.gravity(gravity, image.width, image.height) # apply the composition image.composite(overlay, mode, **options) end
def convert_to_image(object, name)
def convert_to_image(object, name) return object if object.is_a?(::Vips::Image) if object.is_a?(String) path = object elsif object.respond_to?(:to_path) path = object.to_path elsif object.respond_to?(:path) path = object.path else raise ArgumentError, "#{name} must be a Vips::Image, String, Pathname, or respond to #path" end ::Vips::Image.new_from_file(path) end
def default_dimensions(width, height)
def default_dimensions(width, height) raise Error, "either width or height must be specified" unless width || height [width || ::Vips::MAX_COORD, height || ::Vips::MAX_COORD] end
def remove(*args) image.tap { |img| img.remove(*args) } end
def remove(*args) image.tap { |img| img.remove(*args) } end
def resize_and_pad(width, height, gravity: "centre", extend: nil, background: nil, alpha: nil, **options)
Resizes the image to fit within the specified dimensions and fills
def resize_and_pad(width, height, gravity: "centre", extend: nil, background: nil, alpha: nil, **options) image = thumbnail(width, height, **options) image = image.add_alpha if alpha && !image.has_alpha? image.gravity(gravity, width, height, extend: extend, background: background) end
def resize_to_cover(width, height, **options)
Resizes the image to cover the specified dimensions, without
def resize_to_cover(width, height, **options) image = self.image.is_a?(String) ? self.class.load_image(self.image) : self.image image_ratio = Rational(image.width, image.height) thumbnail_ratio = Rational(width, height) if image_ratio > thumbnail_ratio width = ::Vips::MAX_COORD else height = ::Vips::MAX_COORD end thumbnail(width, height, **options, crop: :none) end
def resize_to_fill(width, height, **options)
Resizes the image to fill the specified dimensions, applying any
def resize_to_fill(width, height, **options) thumbnail(width, height, crop: :centre, **options) end
def resize_to_fit(width, height, **options)
def resize_to_fit(width, height, **options) width, height = default_dimensions(width, height) thumbnail(width, height, **options) end
def resize_to_limit(width, height, **options)
def resize_to_limit(width, height, **options) width, height = default_dimensions(width, height) thumbnail(width, height, size: :down, **options) end
def rotate(degrees, **options)
def rotate(degrees, **options) if ([90, 180, 270].include?(degrees) && options.empty?) rot_command = "rot#{degrees}".to_sym image.public_send rot_command else image.similarity(angle: degrees, **options) end end
def set(*args) image.tap { |img| img.set(*args) } end
def set(*args) image.tap { |img| img.set(*args) } end
def set_type(*args) image.tap { |img| img.set_type(*args) } end
def set_type(*args) image.tap { |img| img.set_type(*args) } end
def set_value(*args) image.tap { |img| img.set_value(*args) } end
def set_value(*args) image.tap { |img| img.set_value(*args) } end
def thumbnail(width, height, sharpen: SHARPEN_MASK, **options)
Resizes the image according to the specified parameters, and sharpens
def thumbnail(width, height, sharpen: SHARPEN_MASK, **options) if self.image.is_a?(String) # path # resize on load image = ::Vips::Image.thumbnail(self.image, width, height: height, **options) else # we're already calling Image#autorot when loading the image no_rotate = ::Vips.at_least_libvips?(8, 8) ? { no_rotate: true } : { auto_rotate: false } options = no_rotate.merge(options) image = self.image.thumbnail_image(width, height: height, **options) end image = image.conv(sharpen, precision: :integer) if sharpen image end