mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE - ImageMagick jpeg quality (#11004)
* FEATURE - Add SiteSettings to control JPEG image quality `recompress_original_jpg_quality` - the maximum quality of a newly uploaded file. `image_preview_jpg_quality` - the maximum quality of OptimizedImages
This commit is contained in:
parent
ce76553010
commit
2bcca46cc5
@ -85,6 +85,9 @@ class OptimizedImage < ActiveRecord::Base
|
|||||||
temp_file = Tempfile.new(["discourse-thumbnail", extension])
|
temp_file = Tempfile.new(["discourse-thumbnail", extension])
|
||||||
temp_path = temp_file.path
|
temp_path = temp_file.path
|
||||||
|
|
||||||
|
target_quality = upload.target_image_quality(original_path, SiteSetting.image_preview_jpg_quality)
|
||||||
|
opts = opts.merge(quality: target_quality) if target_quality
|
||||||
|
|
||||||
if upload.extension == "svg"
|
if upload.extension == "svg"
|
||||||
FileUtils.cp(original_path, temp_path)
|
FileUtils.cp(original_path, temp_path)
|
||||||
resized = true
|
resized = true
|
||||||
@ -218,6 +221,10 @@ class OptimizedImage < ActiveRecord::Base
|
|||||||
instructions << "-colors" << opts[:colors].to_s
|
instructions << "-colors" << opts[:colors].to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if opts[:quality]
|
||||||
|
instructions << "-quality" << opts[:quality].to_s
|
||||||
|
end
|
||||||
|
|
||||||
# NOTE: ORDER is important!
|
# NOTE: ORDER is important!
|
||||||
instructions.concat(%W{
|
instructions.concat(%W{
|
||||||
-auto-orient
|
-auto-orient
|
||||||
@ -228,7 +235,6 @@ class OptimizedImage < ActiveRecord::Base
|
|||||||
-interpolate catrom
|
-interpolate catrom
|
||||||
-unsharp 2x0.5+0.7+0
|
-unsharp 2x0.5+0.7+0
|
||||||
-interlace none
|
-interlace none
|
||||||
-quality 98
|
|
||||||
-profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')}
|
-profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')}
|
||||||
#{to}
|
#{to}
|
||||||
})
|
})
|
||||||
@ -240,7 +246,7 @@ class OptimizedImage < ActiveRecord::Base
|
|||||||
from = prepend_decoder!(from, to, opts)
|
from = prepend_decoder!(from, to, opts)
|
||||||
to = prepend_decoder!(to, to, opts)
|
to = prepend_decoder!(to, to, opts)
|
||||||
|
|
||||||
%W{
|
instructions = %W{
|
||||||
convert
|
convert
|
||||||
#{from}[0]
|
#{from}[0]
|
||||||
-auto-orient
|
-auto-orient
|
||||||
@ -250,10 +256,14 @@ class OptimizedImage < ActiveRecord::Base
|
|||||||
-crop #{dimensions}+0+0
|
-crop #{dimensions}+0+0
|
||||||
-unsharp 2x0.5+0.7+0
|
-unsharp 2x0.5+0.7+0
|
||||||
-interlace none
|
-interlace none
|
||||||
-quality 98
|
|
||||||
-profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')}
|
-profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')}
|
||||||
#{to}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts[:quality]
|
||||||
|
instructions << "-quality" << opts[:quality].to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
instructions << to
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.downsize_instructions(from, to, dimensions, opts = {})
|
def self.downsize_instructions(from, to, dimensions, opts = {})
|
||||||
|
@ -269,6 +269,14 @@ class Upload < ActiveRecord::Base
|
|||||||
get_dimension(:thumbnail_height)
|
get_dimension(:thumbnail_height)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def target_image_quality(local_path, test_quality)
|
||||||
|
@file_quality ||= Discourse::Utils.execute_command("identify", "-format", "%Q", local_path).to_i rescue 0
|
||||||
|
|
||||||
|
if @file_quality == 0 || @file_quality > test_quality
|
||||||
|
test_quality
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.sha1_from_short_path(path)
|
def self.sha1_from_short_path(path)
|
||||||
if path =~ /(\/uploads\/short-url\/)([a-zA-Z0-9]+)(\..*)?/
|
if path =~ /(\/uploads\/short-url\/)([a-zA-Z0-9]+)(\..*)?/
|
||||||
self.sha1_from_base62_encoded($2)
|
self.sha1_from_base62_encoded($2)
|
||||||
|
@ -1758,6 +1758,8 @@ en:
|
|||||||
allow_all_attachments_for_group_messages: "Allow all email attachments for group messages."
|
allow_all_attachments_for_group_messages: "Allow all email attachments for group messages."
|
||||||
|
|
||||||
png_to_jpg_quality: "Quality of the converted JPG file (1 is lowest quality, 99 is best quality, 100 to disable)."
|
png_to_jpg_quality: "Quality of the converted JPG file (1 is lowest quality, 99 is best quality, 100 to disable)."
|
||||||
|
recompress_original_jpg_quality: "Quality of uploaded image files (1 is lowest quality, 99 is best quality, 100 to disable)."
|
||||||
|
image_preview_jpg_quality: "Quality of resized image files (1 is lowest quality, 99 is best quality, 100 to disable)."
|
||||||
|
|
||||||
allow_staff_to_upload_any_file_in_pm: "Allow staff members to upload any files in PM."
|
allow_staff_to_upload_any_file_in_pm: "Allow staff members to upload any files in PM."
|
||||||
|
|
||||||
|
@ -1339,6 +1339,14 @@ files:
|
|||||||
default: 95
|
default: 95
|
||||||
min: 1
|
min: 1
|
||||||
max: 100
|
max: 100
|
||||||
|
recompress_original_jpg_quality:
|
||||||
|
default: 90
|
||||||
|
min: 1
|
||||||
|
max: 100
|
||||||
|
image_preview_jpg_quality:
|
||||||
|
default: 90
|
||||||
|
min: 1
|
||||||
|
max: 100
|
||||||
allow_staff_to_upload_any_file_in_pm:
|
allow_staff_to_upload_any_file_in_pm:
|
||||||
default: true
|
default: true
|
||||||
client: true
|
client: true
|
||||||
|
@ -56,7 +56,7 @@ class UploadCreator
|
|||||||
if @image_info.type.to_s == "svg"
|
if @image_info.type.to_s == "svg"
|
||||||
clean_svg!
|
clean_svg!
|
||||||
elsif !Rails.env.test? || @opts[:force_optimize]
|
elsif !Rails.env.test? || @opts[:force_optimize]
|
||||||
convert_to_jpeg! if convert_png_to_jpeg?
|
convert_to_jpeg! if convert_png_to_jpeg? || should_alter_quality?
|
||||||
downsize! if should_downsize?
|
downsize! if should_downsize?
|
||||||
|
|
||||||
return @upload if is_still_too_big?
|
return @upload if is_still_too_big?
|
||||||
@ -202,11 +202,16 @@ class UploadCreator
|
|||||||
from = OptimizedImage.prepend_decoder!(from, nil, filename: "image.#{@image_info.type}")
|
from = OptimizedImage.prepend_decoder!(from, nil, filename: "image.#{@image_info.type}")
|
||||||
to = OptimizedImage.prepend_decoder!(to)
|
to = OptimizedImage.prepend_decoder!(to)
|
||||||
|
|
||||||
|
opts = {}
|
||||||
|
desired_quality = [SiteSetting.png_to_jpg_quality, SiteSetting.recompress_original_jpg_quality].compact.min
|
||||||
|
target_quality = @upload.target_image_quality(from, desired_quality)
|
||||||
|
opts = { quality: target_quality } if target_quality
|
||||||
|
|
||||||
begin
|
begin
|
||||||
execute_convert(from, to)
|
execute_convert(from, to, opts)
|
||||||
rescue
|
rescue
|
||||||
# retry with debugging enabled
|
# retry with debugging enabled
|
||||||
execute_convert(from, to, true)
|
execute_convert(from, to, opts.merge(debug: true))
|
||||||
end
|
end
|
||||||
|
|
||||||
new_size = File.size(jpeg_tempfile.path)
|
new_size = File.size(jpeg_tempfile.path)
|
||||||
@ -237,7 +242,7 @@ class UploadCreator
|
|||||||
execute_convert(from, to)
|
execute_convert(from, to)
|
||||||
rescue
|
rescue
|
||||||
# retry with debugging enabled
|
# retry with debugging enabled
|
||||||
execute_convert(from, to, true)
|
execute_convert(from, to, { debug: true })
|
||||||
end
|
end
|
||||||
|
|
||||||
@file.respond_to?(:close!) ? @file.close! : @file.close
|
@file.respond_to?(:close!) ? @file.close! : @file.close
|
||||||
@ -245,22 +250,26 @@ class UploadCreator
|
|||||||
extract_image_info!
|
extract_image_info!
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_convert(from, to, debug = false)
|
def execute_convert(from, to, opts = {})
|
||||||
command = [
|
command = [
|
||||||
"convert",
|
"convert",
|
||||||
from,
|
from,
|
||||||
"-auto-orient",
|
"-auto-orient",
|
||||||
"-background", "white",
|
"-background", "white",
|
||||||
"-interlace", "none",
|
"-interlace", "none",
|
||||||
"-flatten",
|
"-flatten"
|
||||||
"-quality", SiteSetting.png_to_jpg_quality.to_s
|
|
||||||
]
|
]
|
||||||
command << "-debug" << "all" if debug
|
command << "-debug" << "all" if opts[:debug]
|
||||||
|
command << "-quality" << opts[:quality].to_s if opts[:quality]
|
||||||
command << to
|
command << to
|
||||||
|
|
||||||
Discourse::Utils.execute_command(*command, failure_message: I18n.t("upload.png_to_jpg_conversion_failure_message"))
|
Discourse::Utils.execute_command(*command, failure_message: I18n.t("upload.png_to_jpg_conversion_failure_message"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def should_alter_quality?
|
||||||
|
@upload.target_image_quality(@file.path, SiteSetting.recompress_original_jpg_quality).present?
|
||||||
|
end
|
||||||
|
|
||||||
def should_downsize?
|
def should_downsize?
|
||||||
max_image_size > 0 && filesize >= max_image_size
|
max_image_size > 0 && filesize >= max_image_size
|
||||||
end
|
end
|
||||||
|
@ -123,6 +123,11 @@ RSpec.describe UploadCreator do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe 'converting to jpeg' do
|
describe 'converting to jpeg' do
|
||||||
|
def image_quality(path)
|
||||||
|
local_path = File.join(Rails.root, 'public', path)
|
||||||
|
Discourse::Utils.execute_command("identify", "-format", "%Q", local_path).to_i
|
||||||
|
end
|
||||||
|
|
||||||
let(:filename) { "should_be_jpeg.png" }
|
let(:filename) { "should_be_jpeg.png" }
|
||||||
let(:file) { file_from_fixtures(filename) }
|
let(:file) { file_from_fixtures(filename) }
|
||||||
|
|
||||||
@ -168,6 +173,21 @@ RSpec.describe UploadCreator do
|
|||||||
expect(File.extname(upload.url)).to eq('.jpeg')
|
expect(File.extname(upload.url)).to eq('.jpeg')
|
||||||
expect(upload.original_filename).to eq('should_be_jpeg.jpg')
|
expect(upload.original_filename).to eq('should_be_jpeg.jpg')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should alter the image quality' do
|
||||||
|
SiteSetting.png_to_jpg_quality = 75
|
||||||
|
SiteSetting.recompress_original_jpg_quality = 40
|
||||||
|
SiteSetting.image_preview_jpg_quality = 10
|
||||||
|
|
||||||
|
upload = UploadCreator.new(file, filename, force_optimize: true).create_for(user.id)
|
||||||
|
|
||||||
|
expect(image_quality(upload.url)).to eq(SiteSetting.recompress_original_jpg_quality)
|
||||||
|
|
||||||
|
upload.create_thumbnail!(100, 100)
|
||||||
|
upload.reload
|
||||||
|
|
||||||
|
expect(image_quality(upload.optimized_images.first.url)).to eq(SiteSetting.image_preview_jpg_quality)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'converting HEIF to jpeg' do
|
describe 'converting HEIF to jpeg' do
|
||||||
|
Loading…
Reference in New Issue
Block a user