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 000000000..59b450704 Binary files /dev/null and b/config/sRGB.icm differ diff --git a/test/files/test-animated-86x52.gif b/test/files/test-animated-86x52.gif new file mode 100644 index 000000000..954d073d1 Binary files /dev/null and b/test/files/test-animated-86x52.gif differ diff --git a/test/files/test-static-32x32.gif b/test/files/test-static-32x32.gif new file mode 100644 index 000000000..db461b593 Binary files /dev/null and b/test/files/test-static-32x32.gif differ 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