FIX: crop & optimize user background profile/card images

This commit is contained in:
Régis Hanol
2015-07-15 17:15:43 +02:00
parent 00aaa692ac
commit b0802abae2
9 changed files with 70 additions and 64 deletions

View File

@@ -59,13 +59,7 @@ class UploadsController < ApplicationController
content_type = file.content_type
end
# when we're dealing with an avatar, crop it to its maximum size
if type == "avatar" && FileHelper.is_image?(filename)
max = Discourse.avatar_sizes.max
OptimizedImage.resize(tempfile.path, tempfile.path, max, max, allow_animation: SiteSetting.allow_animated_avatars)
end
upload = Upload.create_for(current_user.id, tempfile, filename, tempfile.size, content_type: content_type)
upload = Upload.create_for(current_user.id, tempfile, filename, tempfile.size, content_type: content_type, image_type: type)
if upload.errors.empty? && current_user.admin?
retain_hours = params[:retain_hours].to_i

View File

@@ -49,13 +49,62 @@ class Upload < ActiveRecord::Base
File.extname(original_filename)
end
# list of image types that will be cropped
CROPPED_IMAGE_TYPES ||= ["avatar", "profile_background", "card_background"]
# options
# - content_type
# - origin
# - image_type
def self.create_for(user_id, file, filename, filesize, options = {})
sha1 = Digest::SHA1.file(file).hexdigest
DistributedMutex.synchronize("upload_#{user_id}_#{filename}") do
# do some work on images
if FileHelper.is_image?(filename)
if filename =~ /\.svg$/i
svg = Nokogiri::XML(file).at_css("svg")
w = svg["width"].to_i
h = svg["height"].to_i
else
# fix orientation first
fix_image_orientation(file.path)
# retrieve image info
image_info = FastImage.new(file) rescue nil
w, h = *(image_info.try(:size) || [0, 0])
end
# default size
width, height = ImageSizer.resize(w, h)
# make sure we're at the beginning of the file (both FastImage and Nokogiri move the pointer)
file.rewind
# crop images depending on their type
if CROPPED_IMAGE_TYPES.include?(options[:image_type])
allow_animation = false
max_pixel_ratio = Discourse::PIXEL_RATIOS.max
case options[:image_type]
when "avatar"
allow_animation = SiteSetting.allow_animated_avatars
width = height = Discourse.avatar_sizes.max
when "profile_background"
max_width = 850 * max_pixel_ratio
width, height = ImageSizer.resize(w, h, max_width: max_width, max_height: max_width)
when "card_background"
max_width = 590 * max_pixel_ratio
width, height = ImageSizer.resize(w, h, max_width: max_width, max_height: max_width)
end
OptimizedImage.resize(file.path, file.path, width, height, allow_animation: allow_animation)
end
# optimize image
ImageOptim.new.optimize_image!(file.path) rescue nil
end
# compute the sha of the file
sha1 = Digest::SHA1.file(file).hexdigest
DistributedMutex.synchronize("upload_#{sha1}") do
# do we already have that upload?
upload = find_by(sha1: sha1)
@@ -75,13 +124,12 @@ class Upload < ActiveRecord::Base
upload.filesize = filesize
upload.sha1 = sha1
upload.url = ""
upload.width = width
upload.height = height
upload.origin = options[:origin][0...1000] if options[:origin]
if FileHelper.is_image?(filename)
# deal with width & height for images
upload = resize_image(filename, file, upload)
# optimize image
ImageOptim.new.optimize_image!(file.path) rescue nil
if FileHelper.is_image?(filename) && (upload.width == 0 || upload.height == 0)
upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
end
return upload unless upload.save
@@ -97,43 +145,10 @@ class Upload < ActiveRecord::Base
end
end
# return the uploaded file
upload
end
end
def self.resize_image(filename, file, upload)
begin
if filename =~ /\.svg$/i
svg = Nokogiri::XML(file).at_css("svg")
width, height = svg["width"].to_i, svg["height"].to_i
if width == 0 || height == 0
upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
else
upload.width, upload.height = ImageSizer.resize(width, height)
end
else
# fix orientation first
Upload.fix_image_orientation(file.path)
# retrieve image info
image_info = FastImage.new(file, raise_on_failure: true)
# compute image aspect ratio
upload.width, upload.height = ImageSizer.resize(*image_info.size)
end
# make sure we're at the beginning of the file
# (FastImage and Nokogiri move the pointer)
file.rewind
rescue FastImage::ImageFetchFailure
upload.errors.add(:base, I18n.t("upload.images.fetch_failure"))
rescue FastImage::UnknownImageType
upload.errors.add(:base, I18n.t("upload.images.unknown_image_type"))
rescue FastImage::SizeNotFound
upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
end
upload
end
def self.get_from_url(url)
return if url.blank?
# we store relative urls, so we need to remove any host/cdn

View File

@@ -20,7 +20,7 @@ class UserAvatar < ActiveRecord::Base
max = Discourse.avatar_sizes.max
gravatar_url = "http://www.gravatar.com/avatar/#{email_hash}.png?s=#{max}&d=404"
tempfile = FileHelper.download(gravatar_url, SiteSetting.max_image_size_kb.kilobytes, "gravatar")
upload = Upload.create_for(user.id, tempfile, 'gravatar.png', tempfile.size, { origin: gravatar_url })
upload = Upload.create_for(user.id, tempfile, 'gravatar.png', tempfile.size, origin: gravatar_url, image_type: "avatar")
if gravatar_upload_id != upload.id
gravatar_upload.try(:destroy!)
@@ -68,7 +68,7 @@ class UserAvatar < ActiveRecord::Base
ext = FastImage.type(tempfile).to_s
tempfile.rewind
upload = Upload.create_for(user.id, tempfile, "external-avatar." + ext, tempfile.size, { origin: avatar_url })
upload = Upload.create_for(user.id, tempfile, "external-avatar." + ext, tempfile.size, origin: avatar_url, image_type: "avatar")
user.uploaded_avatar_id = upload.id
unless user.user_avatar