From dc9b7e5bda20fb16a840a2e40ddc4903ac2c700b Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 26 Mar 2018 20:19:13 -0500 Subject: [PATCH] Fix #3582: Switch from ImageMagick to libvips --- Gemfile | 2 +- Gemfile.lock | 5 ++- INSTALL.debian | 2 +- app/logical/danbooru_image_resizer.rb | 58 ++++++-------------------- app/models/upload.rb | 14 ++++++- config/danbooru_default_config.rb | 4 -- config/sRGB.icm | Bin 0 -> 3268 bytes test/files/test-animated-86x52.gif | Bin 0 -> 1122 bytes test/files/test-static-32x32.gif | Bin 0 -> 408 bytes test/unit/upload_test.rb | 10 +++++ 10 files changed, 39 insertions(+), 56 deletions(-) create mode 100644 config/sRGB.icm create mode 100644 test/files/test-animated-86x52.gif create mode 100644 test/files/test-static-32x32.gif diff --git a/Gemfile b/Gemfile index dafab3015..a0fc953cc 100644 --- a/Gemfile +++ b/Gemfile @@ -18,7 +18,7 @@ gem "simple_form" gem "mechanize" gem "whenever", :require => false gem "sanitize" -gem 'rmagick' +gem 'ruby-vips' gem 'net-sftp' gem 'term-ansicolor', :require => "term/ansicolor" gem 'diff-lcs', :require => "diff/lcs/array" diff --git a/Gemfile.lock b/Gemfile.lock index 5a9b1c022..5ac7139ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -303,8 +303,9 @@ GEM mime-types (>= 1.16, < 3.0) netrc (~> 0.7) retriable (2.1.0) - rmagick (2.16.0) ruby-prof (0.17.0) + ruby-vips (2.0.9) + ffi (~> 1.9) rubyzip (1.2.1) safe_yaml (1.0.4) sanitize (4.5.0) @@ -451,9 +452,9 @@ DEPENDENCIES rakismet recaptcha responders - rmagick ruby-imagespec! ruby-prof + ruby-vips rubyzip sanitize sass-rails diff --git a/INSTALL.debian b/INSTALL.debian index 62f039156..0dbf30570 100644 --- a/INSTALL.debian +++ b/INSTALL.debian @@ -35,7 +35,7 @@ export GITHUB_INSTALL_SCRIPTS=https://raw.githubusercontent.com/r888888888/danbo echo "* Installing packages..." LIBSSL_DEV_PKG=$( verlt `lsb_release -sr` 9.0 && echo libssl-dev || echo libssl1.0-dev ) apt-get update -apt-get -y install build-essential automake $LIBSSL_DEV_PKG libxml2-dev libxslt-dev ncurses-dev sudo libreadline-dev flex bison ragel memcached libmemcached-dev git curl libcurl4-openssl-dev imagemagick libmagickcore-dev libmagickwand-dev sendmail-bin sendmail postgresql postgresql-contrib libpq-dev postgresql-server-dev-all nginx ssh coreutils ffmpeg mkvtoolnix +apt-get -y install build-essential automake $LIBSSL_DEV_PKG libxml2-dev libxslt-dev ncurses-dev sudo libreadline-dev flex bison ragel memcached libmemcached-dev git curl libcurl4-openssl-dev libvips-dev libvips-tools sendmail-bin sendmail postgresql postgresql-contrib libpq-dev postgresql-server-dev-all nginx ssh coreutils ffmpeg mkvtoolnix if [ $? -ne 0 ]; then echo "* Error installing packages; aborting" diff --git a/app/logical/danbooru_image_resizer.rb b/app/logical/danbooru_image_resizer.rb index d3697fbc3..abad0c1a6 100644 --- a/app/logical/danbooru_image_resizer.rb +++ b/app/logical/danbooru_image_resizer.rb @@ -1,52 +1,18 @@ module DanbooruImageResizer - def resize(file, width, height, resize_quality = 90) - image = Magick::Image.read(file.path).first - geometry = "#{width}x>" + # Taken from ArgyllCMS 2.0.0 (see also: https://ninedegreesbelow.com/photography/srgb-profile-comparison.html) + SRGB_PROFILE = "#{Rails.root}/config/sRGB.icm" + # http://jcupitt.github.io/libvips/API/current/libvips-resample.html#vips-thumbnail + THUMBNAIL_OPTIONS = { size: :down, linear: false, auto_rotate: false, export_profile: SRGB_PROFILE } + # http://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave + JPEG_OPTIONS = { background: 255, strip: true, interlace: true, optimize_coding: true } - if width == Danbooru.config.small_image_width - # wider than it is tall - geometry = "#{Danbooru.config.small_image_width}x#{Danbooru.config.small_image_width}>" - end + # https://github.com/jcupitt/libvips/wiki/HOWTO----Image-shrinking + # http://jcupitt.github.io/libvips/API/current/Using-vipsthumbnail.md.html + def self.resize(file, width, height, resize_quality = 90) + output_file = Tempfile.new + resized_image = Vips::Image.thumbnail(file.path, width, height: height, **THUMBNAIL_OPTIONS) + resized_image.jpegsave(output_file.path, Q: resize_quality, **JPEG_OPTIONS) - image.change_geometry(geometry) do |new_width, new_height, img| - img.resize!(new_width, new_height) - width = new_width - height = new_height - end - - image = flatten(image, width, height) - image.strip! - - output_file = Tempfile.new(binmode: true) - image.write("jpeg:" + output_file.path) do - self.quality = resize_quality - # setting PlaneInterlace enables progressive encoding for JPEGs - self.interlace = Magick::PlaneInterlace - end - - image.destroy! output_file end - - def flatten(image, width, height) - if image.alpha? - # since jpeg can't represent transparency, we need to create an image list, - # put a white image on the bottom, then flatten it. - - list = Magick::ImageList.new - list.new_image(width, height) do - self.background_color = "#FFFFFF" - end - list << image - flattened_image = list.flatten_images - list.each do |image| - image.destroy! - end - return flattened_image - else - return image - end - end - - module_function :resize, :flatten end diff --git a/app/models/upload.rb b/app/models/upload.rb index 6b241c4d4..1202b75a6 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -226,7 +226,17 @@ class Upload < ApplicationRecord end def is_animated_gif? - file_ext == "gif" && Magick::Image.ping(file.path).length > 1 + return false if file_ext != "gif" + + # Check whether the gif has multiple frames by trying to load the second frame. + result = Vips::Image.gifload(file.path, page: 1) rescue $ERROR_INFO + if result.is_a?(Vips::Image) + true + elsif result.is_a?(Vips::Error) && result.message.match?(/too few frames in GIF file/) + false + else + raise result + end end def is_animated_png? @@ -245,7 +255,7 @@ class Upload < ApplicationRecord preview_file = DanbooruImageResizer.resize(file, Danbooru.config.small_image_width, Danbooru.config.small_image_width, 85) if image_width > Danbooru.config.large_image_width - sample_file = DanbooruImageResizer.resize(file, Danbooru.config.large_image_width, nil, 90) + sample_file = DanbooruImageResizer.resize(file, Danbooru.config.large_image_width, image_height, 90) end end diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index ecbb9b63f..d481220d6 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -550,10 +550,6 @@ module Danbooru true end - def image_magick_srgb_profile_path - # "/usr/share/ghostscript/9.06/Resource/ColorSpace/sRGB" - end - # For downloads, if the host matches any of these IPs, block it def banned_ip_for_download?(ip_addr) raise ArgumentError unless ip_addr.is_a?(IPAddr) diff --git a/config/sRGB.icm b/config/sRGB.icm new file mode 100644 index 0000000000000000000000000000000000000000..59b4507045831e10007301bab6a8b6e3c0c47ac3 GIT binary patch literal 3268 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 literal 0 HcmV?d00001 diff --git a/test/unit/upload_test.rb b/test/unit/upload_test.rb index cd7184c6f..a2ac2a547 100644 --- a/test/unit/upload_test.rb +++ b/test/unit/upload_test.rb @@ -201,6 +201,16 @@ class UploadTest < ActiveSupport::TestCase @upload = FactoryGirl.build(:upload, file_ext: "png", file: upload_file("test/files/apng/normal_apng.png")) assert_equal("animated_png", @upload.automatic_tags) end + + should "tag animated gif files" do + @upload = FactoryGirl.build(:upload, file_ext: "gif", file: upload_file("test/files/test-animated-86x52.gif")) + assert_equal("animated_gif", @upload.automatic_tags) + end + + should "not tag static gif files" do + @upload = FactoryGirl.build(:upload, file_ext: "gif", file: upload_file("test/files/test-static-32x32.gif")) + assert_equal("", @upload.automatic_tags) + end end end