lib/httpthumbnailer/plugin/thumbnailer.rb in httpthumbnailer-1.1.2 vs lib/httpthumbnailer/plugin/thumbnailer.rb in httpthumbnailer-1.2.0
- old
+ new
@@ -1,20 +1,20 @@
require 'RMagick'
require 'forwardable'
-# ImageMagick Image.mime_type is absolutely bunkers! It goes over file system to look for some strange files WTF?!
-# Also it cannot be used for thumbnails since they are not yet rendered to desired format
-# Here is stupid implementaiton
module MetaData
def width
@image.columns
end
def height
@image.rows
end
+ # ImageMagick Image.mime_type is absolutely bunkers! It goes over file system to look for some strange files WTF?!
+ # Also it cannot be used for thumbnails since they are not yet rendered to desired format
+ # Here is stupid implementation
def mime_type
#TODO: how do I do it better?
format = @format || @image.format
mime = case format
when 'JPG' then 'jpeg'
@@ -92,11 +92,11 @@
@processing_methods = processing_methods
end
def thumbnail(spec)
spec = spec.dup
- # default backgraud is white
+ # default background is white
spec.options['background-color'] = spec.options.fetch('background-color', 'white').sub(/^0x/, '#')
width = spec.width == :input ? @image.columns : spec.width
height = spec.height == :input ? @image.rows : spec.height
@@ -132,11 +132,11 @@
@image.use do |image|
yield self
end
end
- def_delegators :@image, :destroy!, :destroyed?
+ def_delegators :@image, :destroy!, :destroyed?, :format
include MetaData
# We use base values since it might have been loaded with size hint and prescaled
def width
@@ -153,24 +153,36 @@
end
end
class Thumbnail
include ClassLogging
+ extend Forwardable
def initialize(image, format, options = {})
@image = image
@format = format
+
@quality = (options['quality'] or default_quality(format))
@quality &&= @quality.to_i
+
+ @interlace = (options['interlace'] or 'NoInterlace')
+ fail "unsupported interlace: #{@interlace}" unless Magick::InterlaceType.values.map(&:to_s).include? @interlace
+ @interlace = Magick.const_get @interlace.to_sym
end
+ def_delegators :@image, :format
+
def data
+ # export class variables to local scope
format = @format
quality = @quality
+ interlace = @interlace
+
@image.to_blob do
self.format = format
self.quality = quality if quality
+ self.interlace = interlace
end
end
include MetaData
@@ -357,10 +369,30 @@
def set_limit(limit, value)
old = Magick.limit_resource(limit, value)
log.info "changed #{limit} limit from #{old} to #{value} bytes"
old
end
+
+ def setup_default_methods
+ processing_method('crop') do |image, width, height, options|
+ image.resize_to_fill(width, height, (Float(options['float-x']) rescue 0.5), (Float(options['float-y']) rescue 0.5)) if image.columns != width or image.rows != height
+ end
+
+ processing_method('fit') do |image, width, height, options|
+ image.resize_to_fit(width, height) if image.columns != width or image.rows != height
+ end
+
+ processing_method('pad') do |image, width, height, options|
+ image.resize_to_fit(width, height).replace do |resize|
+ resize.render_on_background(options['background-color'], width, height, (Float(options['float-x']) rescue 0.5), (Float(options['float-y']) rescue 0.5))
+ end if image.columns != width or image.rows != height
+ end
+
+ processing_method('limit') do |image, width, height, options|
+ image.resize_to_fit(width, height) if image.columns > width or image.rows > height
+ end
+ end
end
def self.setup(app)
Service.logger = app.logger_for(Service)
InputImage.logger = app.logger_for(InputImage)
@@ -370,27 +402,11 @@
limit_memory: app.settings[:limit_memory],
limit_map: app.settings[:limit_map],
limit_disk: app.settings[:limit_disk]
)
- @@service.processing_method('crop') do |image, width, height, options|
- image.resize_to_fill(width, height) if image.columns != width or image.rows != height
- end
-
- @@service.processing_method('fit') do |image, width, height, options|
- image.resize_to_fit(width, height) if image.columns != width or image.rows != height
- end
-
- @@service.processing_method('pad') do |image, width, height, options|
- image.resize_to_fit(width, height).replace do |resize|
- resize.render_on_background(options['background-color'], width, height)
- end if image.columns != width or image.rows != height
- end
-
- @@service.processing_method('limit') do |image, width, height, options|
- image.resize_to_fit(width, height) if image.columns > width or image.rows > height
- end
+ @@service.setup_default_methods
end
def thumbnailer
@@service
end
@@ -398,33 +414,43 @@
end
class Magick::Image
include Plugin::Thumbnailer::ImageProcessing
- def render_on_background(background_color, width = nil, height = nil)
- Magick::Image.new(width || self.columns, height || self.rows) {
+ def render_on_background(background_color, width = nil, height = nil, float_x = 0.5, float_y = 0.5)
+ # default to image size
+ width ||= self.columns
+ height ||= self.rows
+
+ # make sure we have enough background to fit image on top of it
+ width = self.columns if width < self.columns
+ height = self.rows if height < self.rows
+
+ Magick::Image.new(width, height) {
begin
self.background_color = background_color
rescue ArgumentError
raise Plugin::Thumbnailer::InvalidColorNameError.new(background_color)
end
self.depth = 8
}.replace do |background|
- background.composite!(self, Magick::CenterGravity, Magick::OverCompositeOp)
+ background.composite!(self, *background.float_to_offset(self.columns, self.rows, float_x, float_y), Magick::OverCompositeOp)
end
end
# non coping version
- def resize_to_fill(ncols, nrows = nil, gravity = Magick::CenterGravity)
- nrows ||= ncols
- if ncols != columns or nrows != rows
- scale = [ncols / columns.to_f, nrows / rows.to_f].max
- resize(scale * columns + 0.5, scale * rows + 0.5).replace do |image|
- image.crop(gravity, ncols, nrows, true) if ncols != columns or nrows != rows
- end
- else
- crop(gravity, ncols, nrows, true) if ncols != columns or nrows != rows
+ def resize_to_fill(width, height = nil, float_x = 0.5, float_y = 0.5)
+ # default to square
+ height ||= width
+
+ return if width == columns and height == rows
+
+ scale = [width / columns.to_f, height / rows.to_f].max
+
+ resize((scale * columns).ceil, (scale * rows).ceil).replace do |image|
+ next if width == image.columns and height == image.rows
+ image.crop(*image.float_to_offset(width, height, float_x, float_y), width, height, true)
end
end
def downscale(f)
sample(columns / f, rows / f)
@@ -435,8 +461,24 @@
if columns / new_factor > max_width * 2 and rows / new_factor > max_height * 2
find_downscale_factor(max_width, max_height, factor * 2)
else
factor
end
+ end
+
+ def float_to_offset(float_width, float_height, float_x = 0.5, float_y = 0.5)
+ base_width = self.columns
+ base_height = self.rows
+
+ x = ((base_width - float_width) * float_x).ceil
+ y = ((base_height - float_height) * float_y).ceil
+
+ x = 0 if x < 0
+ x = (base_width - float_width) if x > (base_width - float_width)
+
+ y = 0 if y < 0
+ y = (base_height - float_height) if y > (base_height - float_height)
+
+ [x, y]
end
end