FEATURE: introduce lossy color optimization on resized pngs

This feature ensures optimized images run via pngquant, this results extreme amounts of savings for resized images. Effectively the only impact is that the color palette on small resized images is reduced to 256.

To ensure safety we only apply this optimisation to images smaller than 500k.

This commit also makes a bunch of image specs less fragile.
This commit is contained in:
Sam 2019-01-02 17:19:52 +11:00
parent 2914431729
commit 766e67ce57
5 changed files with 45 additions and 14 deletions

View File

@ -318,9 +318,13 @@ class OptimizedImage < ActiveRecord::Base
convert_with(instructions, to, opts) convert_with(instructions, to, opts)
end end
MAX_PNGQUANT_SIZE = 500_000
def self.convert_with(instructions, to, opts = {}) def self.convert_with(instructions, to, opts = {})
Discourse::Utils.execute_command(*instructions) Discourse::Utils.execute_command(*instructions)
FileHelper.optimize_image!(to)
allow_pngquant = to.downcase.ends_with?(".png") && File.size(to) < MAX_PNGQUANT_SIZE
FileHelper.optimize_image!(to, allow_pngquant: allow_pngquant)
true true
rescue => e rescue => e
if opts[:raise_on_error] if opts[:raise_on_error]

View File

@ -82,7 +82,12 @@ class FileHelper
tmp tmp
end end
def self.optimize_image!(filename) def self.optimize_image!(filename, allow_pngquant: false)
pngquant_options = false
if allow_pngquant
pngquant_options = { allow_lossy: true }
end
ImageOptim.new( ImageOptim.new(
# GLOBAL # GLOBAL
timeout: 15, timeout: 15,
@ -92,7 +97,7 @@ class FileHelper
advpng: false, advpng: false,
pngcrush: false, pngcrush: false,
pngout: false, pngout: false,
pngquant: false, pngquant: pngquant_options,
# JPG # JPG
jpegoptim: { strip: SiteSetting.strip_image_metadata ? "all" : "none" }, jpegoptim: { strip: SiteSetting.strip_image_metadata ? "all" : "none" },
jpegtran: false, jpegtran: false,

BIN
spec/fixtures/images/pngquant.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -98,6 +98,27 @@ RSpec.describe UploadCreator do
end end
end end
describe 'pngquant' do
let(:filename) { "pngquant.png" }
let(:file) { file_from_fixtures(filename) }
it 'should apply pngquant to optimized images' do
upload = UploadCreator.new(file, filename,
pasted: true,
force_optimize: true
).create_for(user.id)
# no optimisation possible without losing details
expect(upload.filesize).to eq(9558)
thumbnail_size = upload.get_optimized_image(upload.width, upload.height, {}).filesize
# pngquant will lose some colors causing some extra size reduction
expect(thumbnail_size).to be < 7500
end
end
describe 'converting to jpeg' do describe 'converting to jpeg' do
let(:filename) { "should_be_jpeg.png" } let(:filename) { "should_be_jpeg.png" }
let(:file) { file_from_fixtures(filename) } let(:file) { file_from_fixtures(filename) }

View File

@ -6,7 +6,7 @@ describe OptimizedImage do
unless ENV["TRAVIS"] unless ENV["TRAVIS"]
describe '.crop' do describe '.crop' do
it 'should work correctly (requires correct version of image optim)' do it 'should produce cropped images' do
tmp_path = "/tmp/cropped.png" tmp_path = "/tmp/cropped.png"
begin begin
@ -17,12 +17,15 @@ describe OptimizedImage do
5 5
) )
fixture_path = "#{Rails.root}/spec/fixtures/images/cropped.png" # we don't want to deal with something new here every time image magick
fixture_hex = Digest::MD5.hexdigest(File.read(fixture_path)) # is upgraded or pngquant is upgraded, lets just test the basics ...
# cropped image should be less than 120 bytes
cropped_hex = Digest::MD5.hexdigest(File.read(tmp_path)) cropped_size = File.size(tmp_path)
expect(cropped_size).to be < 120
expect(cropped_size).to be > 50
expect(cropped_hex).to eq(fixture_hex)
ensure ensure
File.delete(tmp_path) if File.exists?(tmp_path) File.delete(tmp_path) if File.exists?(tmp_path)
end end
@ -128,7 +131,7 @@ describe OptimizedImage do
end end
describe '.downsize' do describe '.downsize' do
it 'should work correctly (requires correct version of image optim)' do it 'should downsize logo' do
tmp_path = "/tmp/downsized.png" tmp_path = "/tmp/downsized.png"
begin begin
@ -138,12 +141,10 @@ describe OptimizedImage do
"100x100\>" "100x100\>"
) )
fixture_path = "#{Rails.root}/spec/fixtures/images/downsized.png" info = FastImage.new(tmp_path)
fixture_hex = Digest::MD5.hexdigest(File.read(fixture_path)) expect(info.size).to eq([100, 27])
expect(File.size(tmp_path)).to be < 2300
downsized_hex = Digest::MD5.hexdigest(File.read(tmp_path))
expect(downsized_hex).to eq(fixture_hex)
ensure ensure
File.delete(tmp_path) if File.exists?(tmp_path) File.delete(tmp_path) if File.exists?(tmp_path)
end end