lib/morandi.rb in morandi-0.9.3 vs lib/morandi.rb in morandi-0.10.0

- old
+ new

@@ -3,262 +3,19 @@ require 'cairo' require 'gdk_pixbuf2' require 'pixbufutils' require 'redeye' +require 'morandi/image_processor' require 'morandi/utils' require 'morandi/image-ops' require 'morandi/redeye' module Morandi - # Your code goes here... - class ImageProcessor - attr_reader :options, :pb - attr_accessor :config - - def valid_jpeg?(filename) - return false unless File.exist?(filename) - return false unless File.size(filename) > 0 - - type, _, _ = Gdk::Pixbuf.get_file_info(filename) - - type.name == 'jpeg' - rescue - false - end - def self.default_icc_path(path) - "#{path}.icc.jpg" - end - - def initialize(file, user_options, local_options={}) - @file = file - - user_options.keys.grep(/^path/).each { |k| user_options.delete(k) } - - # Give priority to user_options - @options = (local_options || {}).merge(user_options || {}) - @local_options = local_options - - @scale_to = @options['output.max'] - @width, @height = @options['output.width'], @options['output.height'] - - load_file = @file - type, width, height = Gdk::Pixbuf.get_file_info(load_file) - - if type.name.eql?('jpeg') - icc_file = local_options['path.icc'] || ImageProcessor.default_icc_path(@file) - if valid_jpeg?(icc_file) || system("jpgicc", "-q97", @file, icc_file) - load_file = icc_file - end - end - - if @scale_to - @pb = Gdk::Pixbuf.new(load_file, @scale_to, @scale_to) - @src_max = [width, height].max - @actual_max = [@pb.width, @pb.height].max - else - @pb = Gdk::Pixbuf.new(load_file) - @src_max = [@pb.width, @pb.height].max - @actual_max = [@pb.width, @pb.height].max - end - - @scale = @actual_max / @src_max.to_f - end - - def process! - # Apply Red-Eye corrections - apply_redeye! - - # Apply contrast, brightness etc - apply_colour_manipulations! - - # apply rotation - apply_rotate! - - # apply crop - apply_crop! - - # apply filter - apply_filters! - - # add border - apply_decorations! - - if @options['output.limit'] && @width && @height - @pb = @pb.scale_max([@width, @height].max) - end - - @pb - end - - SHARPEN = [ - -1, -1, -1, -1, -1, - -1, 2, 2, 2, -1, - -1, 2, 8, 2, -1, - -1, 2, 2, 2, -1, - -1, -1, -1, -1, -1, - ] - BLUR = [ - 0, 1, 1, 1, 0, - 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - 0, 1, 1, 1, 0, - ] - - def apply_colour_manipulations! - #STDERR.puts "FILTER: #{options.inspect}" - if options['brighten'].to_i.nonzero? - brighten = [ [ 5 * options['brighten'], -100 ].max, 100 ].min - #STDERR.puts([:brighten, brighten].inspect) - @pb = PixbufUtils.brightness(@pb, brighten) - end - if options['gamma'] && (options['gamma'] != 1.0) - @pb = PixbufUtils.gamma(@pb, options['gamma']) - end - if options['contrast'].to_i.nonzero? - @pb = PixbufUtils.contrast(@pb, [ [ 5 * options['contrast'], -100 ].max, 100 ].min) - end - if options['sharpen'].to_i.nonzero? - if options['sharpen'] > 0 - [options['sharpen'], 5].min.times do - @pb = PixbufUtils.filter(@pb, SHARPEN, SHARPEN.inject(0, &:+)) - end - elsif options['sharpen'] < 0 - [ (options['sharpen']*-1), 5].min.times do - @pb = PixbufUtils.filter(@pb, BLUR, BLUR.inject(0, &:+)) - end - end - end - end - - def apply_redeye! - for eye in options['redeye'] || [] - @pb = Morandi::RedEye.tap_on(@pb, eye[0] * @scale, eye[1] * @scale) - end - end - - def angle - a = options['angle'].to_i - if a - (360-a)%360 - else - nil - end - end - - # modifies @pb with any applied rotation - def apply_rotate! - a = angle() - - unless (a%360).zero? - @pb = @pb.rotate(a) - end - - unless options['straighten'].to_f.zero? - @pb = Morandi::Straighten.new(options['straighten'].to_f).call(nil, @pb) - end - - @image_width = @pb.width - @image_height = @pb.height - end - - DEFAULT_CONFIG = { - 'border-size-mm' => 5 - } - def config_for(key) - return options[key] if options && options.has_key?(key) - return @config[key] if @config && @config.has_key?(key) - DEFAULT_CONFIG[key] - end - - # - def apply_crop! - crop = options['crop'] - - if crop.nil? && config_for('image.auto-crop').eql?(false) - return - end - - if crop.is_a?(String) && crop =~ /^\d+,\d+,\d+,\d+/ - crop = crop.split(/,/).map(&:to_i) - end - - crop = nil unless crop.is_a?(Array) && crop.size.eql?(4) && crop.all? { |i| - i.kind_of?(Numeric) - } - - # can't crop, won't crop - return if @width.nil? && @height.nil? && crop.nil? - - if crop && @scale != 1.0 - crop = crop.map { |s| (s.to_f * @scale).floor } - end - - crop ||= Morandi::Utils.autocrop_coords(@pb.width, @pb.height, @width, @height) - - @pb = Morandi::Utils.apply_crop(@pb, crop[0], crop[1], crop[2], crop[3]) - end - - - def apply_filters! - filter = options['fx'] - - case filter - when 'greyscale' - op = Morandi::Colourify.new_from_hash('op' => filter) - when 'sepia', 'bluetone' - op = Morandi::Colourify.new_from_hash('op' => filter, 'alpha' => (0.85 * 255).to_i) - else - return - end - @pb = op.call(nil, @pb) - end - - def apply_decorations! - style, colour = options['border-style'], options['background-style'] - - return if style.nil? or style.eql?('none') - return if colour.eql?('none') - colour ||= 'black' - - crop = options['crop'] - crop = crop.map { |s| (s.to_f * @scale).floor } if crop && @scale != 1.0 - - op = Morandi::ImageBorder.new_from_hash(data={ - 'style' => style, - 'colour' => colour || '#000000', - 'crop' => crop, - 'size' => [@image_width, @image_height], - 'print_size' => [@width, @height], - 'shrink' => true, - 'border_size' => @scale * config_for('border-size-mm').to_i * 300 / 25.4 # 5mm at 300dpi - }) - - @pb = op.call(nil, @pb) - end - - def write_to_png(fn, orientation=:any) - pb = @pb - - case orientation - when :landscape - pb = @pb.rotate(90) if @pb.width < @pb.height - when :portrait - pb = @pb.rotate(90) if @pb.width > @pb.height - end - pb.save(fn, 'png') - end - - def write_to_jpeg(fn, quality = 97) - @pb.save(fn, 'jpeg', :quality => quality) - end -end - module_function def process(file_in, options, out_file, local_options = {}) pro = ImageProcessor.new(file_in, options, local_options) pro.process! pro.write_to_jpeg(out_file) end - end +