407 lines
12 KiB
Ruby
407 lines
12 KiB
Ruby
module CarrierWave
|
|
|
|
##
|
|
# This module simplifies manipulation with RMagick by providing a set
|
|
# of convenient helper methods. If you want to use them, you'll need to
|
|
# require this file:
|
|
#
|
|
# require 'carrierwave/processing/rmagick'
|
|
#
|
|
# And then include it in your uploader:
|
|
#
|
|
# class MyUploader < CarrierWave::Uploader::Base
|
|
# include CarrierWave::RMagick
|
|
# end
|
|
#
|
|
# You can now use the provided helpers:
|
|
#
|
|
# class MyUploader < CarrierWave::Uploader::Base
|
|
# include CarrierWave::RMagick
|
|
#
|
|
# process :resize_to_fit => [200, 200]
|
|
# end
|
|
#
|
|
# Or create your own helpers with the powerful manipulate! method. Check
|
|
# out the RMagick docs at http://www.imagemagick.org/RMagick/doc/ for more
|
|
# info
|
|
#
|
|
# class MyUploader < CarrierWave::Uploader::Base
|
|
# include CarrierWave::RMagick
|
|
#
|
|
# process :do_stuff => 10.0
|
|
#
|
|
# def do_stuff(blur_factor)
|
|
# manipulate! do |img|
|
|
# img = img.sepiatone
|
|
# img = img.auto_orient
|
|
# img = img.radial_blur(blur_factor)
|
|
# end
|
|
# end
|
|
# end
|
|
#
|
|
# === Note
|
|
#
|
|
# You should be aware how RMagick handles memory. manipulate! takes care
|
|
# of freeing up memory for you, but for optimum memory usage you should
|
|
# use destructive operations as much as possible:
|
|
#
|
|
# DON'T DO THIS:
|
|
# img = img.resize_to_fit
|
|
#
|
|
# DO THIS INSTEAD:
|
|
# img.resize_to_fit!
|
|
#
|
|
# Read this for more information why:
|
|
#
|
|
# http://rubyforge.org/forum/forum.php?thread_id=1374&forum_id=1618
|
|
#
|
|
module RMagick
|
|
extend ActiveSupport::Concern
|
|
|
|
included do
|
|
begin
|
|
require "rmagick"
|
|
rescue LoadError
|
|
require "RMagick"
|
|
rescue LoadError => e
|
|
e.message << " (You may need to install the rmagick gem)"
|
|
raise e
|
|
end
|
|
|
|
prepend Module.new {
|
|
def initialize(*)
|
|
super
|
|
@format = nil
|
|
end
|
|
}
|
|
end
|
|
|
|
module ClassMethods
|
|
def convert(format)
|
|
process :convert => format
|
|
end
|
|
|
|
def resize_to_limit(width, height)
|
|
process :resize_to_limit => [width, height]
|
|
end
|
|
|
|
def resize_to_fit(width, height)
|
|
process :resize_to_fit => [width, height]
|
|
end
|
|
|
|
def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
|
|
process :resize_to_fill => [width, height, gravity]
|
|
end
|
|
|
|
def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
|
|
process :resize_and_pad => [width, height, background, gravity]
|
|
end
|
|
|
|
def resize_to_geometry_string(geometry_string)
|
|
process :resize_to_geometry_string => [geometry_string]
|
|
end
|
|
end
|
|
|
|
##
|
|
# Changes the image encoding format to the given format
|
|
#
|
|
# See even http://www.imagemagick.org/RMagick/doc/magick.html#formats
|
|
#
|
|
# === Parameters
|
|
#
|
|
# [format (#to_s)] an abbreviation of the format
|
|
#
|
|
# === Yields
|
|
#
|
|
# [Magick::Image] additional manipulations to perform
|
|
#
|
|
# === Examples
|
|
#
|
|
# image.convert(:png)
|
|
#
|
|
def convert(format)
|
|
manipulate!(:format => format)
|
|
@format = format
|
|
end
|
|
|
|
##
|
|
# Resize the image to fit within the specified dimensions while retaining
|
|
# the original aspect ratio. Will only resize the image if it is larger than the
|
|
# specified dimensions. The resulting image may be shorter or narrower than specified
|
|
# in the smaller dimension but will not be larger than the specified values.
|
|
#
|
|
# === Parameters
|
|
#
|
|
# [width (Integer)] the width to scale the image to
|
|
# [height (Integer)] the height to scale the image to
|
|
#
|
|
# === Yields
|
|
#
|
|
# [Magick::Image] additional manipulations to perform
|
|
#
|
|
def resize_to_limit(width, height)
|
|
width = dimension_from width
|
|
height = dimension_from height
|
|
manipulate! do |img|
|
|
geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
|
|
new_img = img.change_geometry(geometry) do |new_width, new_height|
|
|
img.resize(new_width, new_height)
|
|
end
|
|
destroy_image(img)
|
|
new_img = yield(new_img) if block_given?
|
|
new_img
|
|
end
|
|
end
|
|
|
|
##
|
|
# From the RMagick documentation: "Resize the image to fit within the
|
|
# specified dimensions while retaining the original aspect ratio. The
|
|
# image may be shorter or narrower than specified in the smaller dimension
|
|
# but will not be larger than the specified values."
|
|
#
|
|
# See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fit
|
|
#
|
|
# === Parameters
|
|
#
|
|
# [width (Integer)] the width to scale the image to
|
|
# [height (Integer)] the height to scale the image to
|
|
#
|
|
# === Yields
|
|
#
|
|
# [Magick::Image] additional manipulations to perform
|
|
#
|
|
def resize_to_fit(width, height)
|
|
width = dimension_from width
|
|
height = dimension_from height
|
|
manipulate! do |img|
|
|
img.resize_to_fit!(width, height)
|
|
img = yield(img) if block_given?
|
|
img
|
|
end
|
|
end
|
|
|
|
##
|
|
# From the RMagick documentation: "Resize the image to fit within the
|
|
# specified dimensions while retaining the aspect ratio of the original
|
|
# image. If necessary, crop the image in the larger dimension."
|
|
#
|
|
# See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fill
|
|
#
|
|
# === Parameters
|
|
#
|
|
# [width (Integer)] the width to scale the image to
|
|
# [height (Integer)] the height to scale the image to
|
|
#
|
|
# === Yields
|
|
#
|
|
# [Magick::Image] additional manipulations to perform
|
|
#
|
|
def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
|
|
width = dimension_from width
|
|
height = dimension_from height
|
|
manipulate! do |img|
|
|
img.crop_resized!(width, height, gravity)
|
|
img = yield(img) if block_given?
|
|
img
|
|
end
|
|
end
|
|
|
|
##
|
|
# Resize the image to fit within the specified dimensions while retaining
|
|
# the original aspect ratio. If necessary, will pad the remaining area
|
|
# with the given color, which defaults to transparent (for gif and png,
|
|
# white for jpeg).
|
|
#
|
|
# === Parameters
|
|
#
|
|
# [width (Integer)] the width to scale the image to
|
|
# [height (Integer)] the height to scale the image to
|
|
# [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de"
|
|
# [gravity (Magick::GravityType)] how to position the image
|
|
#
|
|
# === Yields
|
|
#
|
|
# [Magick::Image] additional manipulations to perform
|
|
#
|
|
def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
|
|
width = dimension_from width
|
|
height = dimension_from height
|
|
manipulate! do |img|
|
|
img.resize_to_fit!(width, height)
|
|
new_img = ::Magick::Image.new(width, height) { |img| img.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
|
|
if background == :transparent
|
|
filled = new_img.matte_floodfill(1, 1)
|
|
else
|
|
filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
|
|
end
|
|
destroy_image(new_img)
|
|
filled.composite!(img, gravity, ::Magick::OverCompositeOp)
|
|
destroy_image(img)
|
|
filled = yield(filled) if block_given?
|
|
filled
|
|
end
|
|
end
|
|
|
|
##
|
|
# Resize the image per the provided geometry string.
|
|
#
|
|
# === Parameters
|
|
#
|
|
# [geometry_string (String)] the proportions in which to scale image
|
|
#
|
|
# === Yields
|
|
#
|
|
# [Magick::Image] additional manipulations to perform
|
|
#
|
|
def resize_to_geometry_string(geometry_string)
|
|
manipulate! do |img|
|
|
new_img = img.change_geometry(geometry_string) do |new_width, new_height|
|
|
img.resize(new_width, new_height)
|
|
end
|
|
destroy_image(img)
|
|
new_img = yield(new_img) if block_given?
|
|
new_img
|
|
end
|
|
end
|
|
|
|
##
|
|
# Returns the width of the image.
|
|
#
|
|
# === Returns
|
|
#
|
|
# [Integer] the image's width in pixels
|
|
#
|
|
def width
|
|
rmagick_image.columns
|
|
end
|
|
|
|
##
|
|
# Returns the height of the image.
|
|
#
|
|
# === Returns
|
|
#
|
|
# [Integer] the image's height in pixels
|
|
#
|
|
def height
|
|
rmagick_image.rows
|
|
end
|
|
|
|
##
|
|
# Manipulate the image with RMagick. This method will load up an image
|
|
# and then pass each of its frames to the supplied block. It will then
|
|
# save the image to disk.
|
|
#
|
|
# === Gotcha
|
|
#
|
|
# This method assumes that the object responds to +current_path+.
|
|
# Any class that this module is mixed into must have a +current_path+ method.
|
|
# CarrierWave::Uploader does, so you won't need to worry about this in
|
|
# most cases.
|
|
#
|
|
# === Yields
|
|
#
|
|
# [Magick::Image] manipulations to perform
|
|
# [Integer] Frame index if the image contains multiple frames
|
|
# [Hash] options, see below
|
|
#
|
|
# === Options
|
|
#
|
|
# The options argument to this method is also yielded as the third
|
|
# block argument.
|
|
#
|
|
# Currently, the following options are defined:
|
|
#
|
|
# ==== :write
|
|
# A hash of assignments to be evaluated in the block given to the RMagick write call.
|
|
#
|
|
# An example:
|
|
#
|
|
# manipulate! do |img, index, options|
|
|
# options[:write] = {
|
|
# :quality => 50,
|
|
# :depth => 8
|
|
# }
|
|
# img
|
|
# end
|
|
#
|
|
# This will translate to the following RMagick::Image#write call:
|
|
#
|
|
# image.write do |img|
|
|
# self.quality = 50
|
|
# self.depth = 8
|
|
# end
|
|
#
|
|
# ==== :read
|
|
# A hash of assignments to be given to the RMagick read call.
|
|
#
|
|
# The options available are identical to those for write, but are passed in directly, like this:
|
|
#
|
|
# manipulate! :read => { :density => 300 }
|
|
#
|
|
# ==== :format
|
|
# Specify the output format. If unset, the filename extension is used to determine the format.
|
|
#
|
|
# === Raises
|
|
#
|
|
# [CarrierWave::ProcessingError] if manipulation failed.
|
|
#
|
|
def manipulate!(options={}, &block)
|
|
cache_stored_file! if !cached?
|
|
|
|
read_block = create_info_block(options[:read])
|
|
image = ::Magick::Image.read(current_path, &read_block)
|
|
frames = ::Magick::ImageList.new
|
|
|
|
image.each_with_index do |frame, index|
|
|
frame = yield(*[frame, index, options].take(block.arity)) if block_given?
|
|
frames << frame if frame
|
|
end
|
|
frames.append(true) if block_given?
|
|
|
|
write_block = create_info_block(options[:write])
|
|
|
|
if options[:format] || @format
|
|
frames.write("#{options[:format] || @format}:#{current_path}", &write_block)
|
|
move_to = current_path.chomp(File.extname(current_path)) + ".#{options[:format] || @format}"
|
|
file.content_type = Marcel::Magic.by_path(move_to).try(:type)
|
|
file.move_to(move_to, permissions, directory_permissions)
|
|
else
|
|
frames.write(current_path, &write_block)
|
|
end
|
|
|
|
destroy_image(frames)
|
|
rescue ::Magick::ImageMagickError
|
|
raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.processing_error")
|
|
end
|
|
|
|
private
|
|
|
|
def create_info_block(options)
|
|
return nil unless options
|
|
proc do |img|
|
|
options.each do |k, v|
|
|
if v.is_a?(String) && (matches = v.match(/^["'](.+)["']/))
|
|
ActiveSupport::Deprecation.warn "Passing quoted strings like #{v} to #manipulate! is deprecated, pass them without quoting."
|
|
v = matches[1]
|
|
end
|
|
img.public_send(:"#{k}=", v)
|
|
end
|
|
end
|
|
end
|
|
|
|
def destroy_image(image)
|
|
image.try(:destroy!)
|
|
end
|
|
|
|
def dimension_from(value)
|
|
return value unless value.instance_of?(Proc)
|
|
value.arity >= 1 ? value.call(self) : value.call
|
|
end
|
|
|
|
def rmagick_image
|
|
::Magick::Image.from_blob(self.read).first
|
|
end
|
|
|
|
end # RMagick
|
|
end # CarrierWave
|