class RailsImager::ImagesController < ApplicationController PARAMS_ARGS = [:width, :height, :smartsize, :maxwidth, :maxheight, :rounded_corners, :border, :border_color, :force] def show set_and_validate_parameters set_path @mod_time = File.mtime(@full_path) generate_cache_name generate_cache if should_generate_cache? set_headers if not_modified? && !force? render :nothing => true, :status => "304 Not Modified" else send_file @cache_path, :type => "image/png", :disposition => "inline", :filename => "picture.png" end end private def set_path @path = "#{Rails.public_path}/#{params[:id]}" @full_path = File.realpath(@path) validate_path end def set_and_validate_parameters @image_params = params[:image] || {} @image_params.each do |key, val| raise ArgumentError, "Invalid parameter: '#{key}'." unless PARAMS_ARGS.map{ |param| param.to_s }.include?(key) end end def force? if @image_params[:force] && @image_params[:force].to_s == "true" return true else return false end end def not_modified? if request.headers["HTTP_IF_MODIFIED_SINCE"] if_mod_since_time = Datet.in(request.headers["HTTP_IF_MODIFIED_SINCE"]).time else if_mod_since_time = request.if_modified_since end if if_mod_since_time && if_mod_since_time.utc.to_s == @mod_time.utc.to_s return true end return false end def set_headers response.last_modified = @mod_time if force? response.headers["Expires"] = Time.now.httpdate else response.headers["Expires"] = 2.hours.from_now.httpdate end end def validate_path allowed_paths = RailsImager.config.allowed_paths allowed = false allowed_paths.each do |allowed_path| if @full_path.start_with?(allowed_path) allowed = true break end end raise ArgumentError, "Image wasn't in an allowed path: '#{@full_path}', allowed paths: #{allowed_paths}." unless allowed end def generate_cache_name @cache_name = ::Knj::Strings.sanitize_filename(@path) @cache_name << "__ARGS_" PARAMS_ARGS.each do |val| next if val.to_s == "force" @cache_name << "_#{val}-#{@image_params[val]}" end @cache_path = "#{cache_directory}/#{@cache_name}" end def cache_directory require "tmpdir" cache_path = "#{Dir.tmpdir}/rails-imager-cache" Dir.mkdir(cache_path) unless Dir.exists?(cache_path) return cache_path end def should_generate_cache? return true if force? if File.exists?(@cache_path) if File.mtime(@cache_path) < File.mtime(@full_path) return true else return false end else return true end end def generate_cache @image = ::Magick::Image.read(@full_path).first @image.format = "png" apply_image_changes @image.write(@cache_path) end #Create a new image-object based on the given image-object and the parameters. def apply_image_changes @image_width = @image.columns @image_height = @image.rows if @image_params[:width].to_i > 0 @width = @image_params[:width].to_i else @width = @image_width end if @image_params[:height].to_i > 0 @height = @image_params[:height].to_i else @height = @image_height end calcuate_sizes @image = @image.resize(@width, @height) if @width != @image_width || @height != @image_height apply_rounded_corners if @image_params[:rounded_corners] end def calcuate_sizes validate_and_set_smartsize if @image_params[:smartsize] validate_and_set_max_width validate_and_set_max_height calculate_missing_width if @image_params[:height] && !@image_params[:width] calculate_missing_height if @image_params[:width] && !@image_params[:height] end def calculate_missing_height @height = (@image_height.to_f / (@image_width.to_f / @width.to_f)).to_i end def calculate_missing_width @width = (@image_width.to_f / (@image_height.to_f / @height.to_f)).to_i end def validate_and_set_smartsize if @image_width > @image_height @width = @image_params[:smartsize].to_i calculate_missing_height else @height = @image_params[:smartsize].to_i calculate_missing_width end end def validate_and_set_max_width if @image_params[:maxwidth] maxwidth = @image_params[:maxwidth].to_i if @width > maxwidth @width = maxwidth calculate_missing_height end end end def validate_and_set_max_height if @image_params[:maxheight] maxheight = @image_params[:maxheight].to_i if @height > maxheight @height = maxheight calculate_missing_width end end end def apply_rounded_corners @image = @image.clone @image.format = "png" # Needs PNG format for transparency. args = {:img => @image, :radius => @image_params[:rounded_corners].to_i} if @image_params[:border] && @image_params[:border_color] args[:border] = @image_params[:border].to_i args[:border_color] = @image_params[:border_color] end ::Knj::Image.rounded_corners(args) end end