From 64446d49e105d1253c65d5c5ad1218f612a9d046 Mon Sep 17 00:00:00 2001 From: Albert Yi Date: Wed, 16 May 2018 17:13:36 -0700 Subject: [PATCH] add image cropping support --- Procfile | 2 +- app/assets/javascripts/responsive.js | 3 +- .../stylesheets/specific/z_responsive.scss | 33 +- app/logical/danbooru_image_resizer.rb | 38 ++ app/logical/image_cropper.rb | 12 - app/logical/pixiv_ugoira_converter.rb | 12 +- app/logical/storage_manager.rb | 6 + app/logical/storage_manager/local.rb | 1 + app/logical/upload_service.rb | 18 +- app/models/post.rb | 4 + app/models/user.rb | 2 + app/presenters/post_presenter.rb | 10 +- app/presenters/post_set_presenters/base.rb | 2 +- app/views/layouts/default.html.erb | 7 +- app/views/posts/index.html.erb | 2 +- .../posts/partials/common/_search.html.erb | 2 +- app/views/users/edit.html.erb | 2 +- db/populate.rb | 11 +- test/models/upload_service_test.rb | 453 ++---------------- test/unit/post_test.rb | 1 - 20 files changed, 150 insertions(+), 471 deletions(-) delete mode 100644 app/logical/image_cropper.rb diff --git a/Procfile b/Procfile index 11556b87b..c8f3a7913 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ unicorn: bundle exec rails server -jobs: bundle exec script/delayed_job run +jobs: bundle exec rake jobs:work diff --git a/app/assets/javascripts/responsive.js b/app/assets/javascripts/responsive.js index 930139652..c5c280f22 100644 --- a/app/assets/javascripts/responsive.js +++ b/app/assets/javascripts/responsive.js @@ -1,6 +1,7 @@ $(function() { $("#maintoggle").click(function() { $('#nav').toggle(); - $('#maintoggle').toggleClass('toggler-active'); + $('#maintoggle-on').toggle(); + $('#maintoggle-off').toggle(); }); }); diff --git a/app/assets/stylesheets/specific/z_responsive.scss b/app/assets/stylesheets/specific/z_responsive.scss index ad7c26d16..8dc06e279 100644 --- a/app/assets/stylesheets/specific/z_responsive.scss +++ b/app/assets/stylesheets/specific/z_responsive.scss @@ -21,6 +21,16 @@ display: none; } + div#page aside#sidebar { + input#tags { + width: 75%; + } + + input[type=submit] { + width: 20%; + } + } + div#page { padding: 0 0.25vw; > div /* div#c-$controller */ { @@ -40,12 +50,12 @@ } #maintoggle { - display: inline; - background-color: lighten($link_color, 13%); - padding: 0.8em 1.2em 0.8em 1.2em; - border-radius: 20% 20% 0 0; + display: block; font-weight: bold; - color: #fff; + position: absolute; + top: 3vw; + right: 4vw; + font-size: 2em; &.toggler-active { background-color: lighten($link_color, 25%); @@ -128,8 +138,8 @@ article.post-preview { margin: 0.5vw; - width: 48.5vw; - height: 48.5vw; + width: 32vw; + height: 32vw; text-align: center; vertical-align: middle; display: inline-block; @@ -139,10 +149,10 @@ //display: block; margin: 0 auto; } - img { + img.cropped { //object-fit: contain; - //width: 48.5vw; - //height: 48.5vw; + width: 32vw; + height: 32vw; margin: 0 auto; border: none !important; } @@ -190,6 +200,8 @@ } #nav { + font-size: 2em; + line-height: 2em; display: none; } @@ -210,7 +222,6 @@ } header { - border-bottom: 2px solid lighten($link_color, 13%); text-align: center; line-height: 2em; h1 { diff --git a/app/logical/danbooru_image_resizer.rb b/app/logical/danbooru_image_resizer.rb index fad396079..cb1f0be31 100644 --- a/app/logical/danbooru_image_resizer.rb +++ b/app/logical/danbooru_image_resizer.rb @@ -5,6 +5,7 @@ module DanbooruImageResizer 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 } + CROP_OPTIONS = { linear: false, auto_rotate: false, export_profile: SRGB_PROFILE, crop: :attention } # XXX libvips-8.4 on Debian doesn't support the `Vips::Image.thumbnail` method. # On 8.4 we have to shell out to vipsthumbnail instead. Remove when Debian supports 8.5. @@ -16,6 +17,14 @@ module DanbooruImageResizer end end + def self.crop(file, length, quality = 90) + if Vips.at_least_libvips?(8, 5) + crop_ruby(file, length, quality) + else + crop_shell(file, length, quality) + end + end + # https://github.com/jcupitt/libvips/wiki/HOWTO----Image-shrinking # http://jcupitt.github.io/libvips/API/current/Using-vipsthumbnail.md.html def self.resize_ruby(file, width, height, resize_quality) @@ -26,6 +35,14 @@ module DanbooruImageResizer output_file end + def self.crop_ruby(file, length, resize_quality) + output_file = Tempfile.new + resized_image = Vips::Image.thumbnail(file.path, length, height: length, **CROP_OPTIONS) + resized_image.jpegsave(output_file.path, Q: resize_quality, **JPEG_OPTIONS) + + output_file + end + def self.resize_shell(file, width, height, quality) output_file = Tempfile.new(["resize", ".jpg"]) @@ -47,4 +64,25 @@ module DanbooruImageResizer output_file end + + def self.crop_shell(file, length, quality) + output_file = Tempfile.new(["crop", ".jpg"]) + + # --size=WxH will upscale if the image is smaller than the target size. + # Fix the target size so that it's not bigger than the image. + image = Vips::Image.new_from_file(file.path) + + arguments = [ + file.path, + "--eprofile=#{SRGB_PROFILE}", + "--crop=none", + "--size=#{length}", + "--format=#{output_file.path}[Q=#{quality},background=255,strip,interlace,optimize_coding]" + ] + + success = system("vipsthumbnail", *arguments) + raise RuntimeError, "vipsthumbnail failed (exit status: #{$?.exitstatus})" if !success + + output_file + end end diff --git a/app/logical/image_cropper.rb b/app/logical/image_cropper.rb deleted file mode 100644 index ff03f4bbe..000000000 --- a/app/logical/image_cropper.rb +++ /dev/null @@ -1,12 +0,0 @@ -class ImageCropper - def self.enabled? - Danbooru.config.aws_sqs_cropper_url.present? - end - - def self.notify(post) - if post.is_image? - sqs = SqsService.new(Danbooru.config.aws_sqs_cropper_url) - sqs.send_message("#{post.id},https://#{Danbooru.config.hostname}/data/#{post.md5}.#{post.file_ext}") - end - end -end diff --git a/app/logical/pixiv_ugoira_converter.rb b/app/logical/pixiv_ugoira_converter.rb index 791e14642..a3608682d 100644 --- a/app/logical/pixiv_ugoira_converter.rb +++ b/app/logical/pixiv_ugoira_converter.rb @@ -64,8 +64,18 @@ class PixivUgoiraConverter output_file end + def self.generate_crop(ugoira_file) + file = Tempfile.new(["ugoira-crop", ".zip"], binmode: true) + zipfile = Zip::File.new(ugoira_file.path) + zipfile.entries.first.extract(file.path) { true } # 'true' means overwrite the existing tempfile. + + DanbooruImageResizer.crop(file, Danbooru.config.small_image_width, 85) + ensure + file.close! + end + def self.generate_preview(ugoira_file) - file = Tempfile.new(binmode: true) + file = Tempfile.new(["ugoira-preview", ".zip"], binmode: true) zipfile = Zip::File.new(ugoira_file.path) zipfile.entries.first.extract(file.path) { true } # 'true' means overwrite the existing tempfile. diff --git a/app/logical/storage_manager.rb b/app/logical/storage_manager.rb index 80e0bdd09..879f6dd5a 100644 --- a/app/logical/storage_manager.rb +++ b/app/logical/storage_manager.rb @@ -57,6 +57,8 @@ class StorageManager "#{root_url}/images/download-preview.png" elsif type == :preview "#{base_url}/preview/#{subdir}#{file}" + elsif type == :crop + "#{base_url}/crop/#{subdir}#{file}" elsif type == :large && post.has_large? "#{base_url}/sample/#{subdir}#{seo_tags}#{file}" else @@ -77,6 +79,8 @@ class StorageManager case type when :preview "#{base_dir}/preview/#{subdir}#{file}" + when :crop + "#{base_dir}/crop/#{subdir}#{file}" when :large "#{base_dir}/sample/#{subdir}#{file}" when :original @@ -90,6 +94,8 @@ class StorageManager case type when :preview "#{md5}.jpg" + when :crop + "#{md5}.jpg" when :large "#{large_image_prefix}#{md5}.#{large_file_ext}" when :original diff --git a/app/logical/storage_manager/local.rb b/app/logical/storage_manager/local.rb index 6bdfa69e3..881ce7ce0 100644 --- a/app/logical/storage_manager/local.rb +++ b/app/logical/storage_manager/local.rb @@ -5,6 +5,7 @@ class StorageManager::Local < StorageManager temp_path = dest_path + "-" + SecureRandom.uuid + ".tmp" FileUtils.mkdir_p(File.dirname(temp_path)) + io.rewind bytes_copied = IO.copy_stream(io, temp_path) raise Error, "store failed: #{bytes_copied}/#{io.size} bytes copied" if bytes_copied != io.size diff --git a/app/logical/upload_service.rb b/app/logical/upload_service.rb index 9277a8ee2..caad5dcb0 100644 --- a/app/logical/upload_service.rb +++ b/app/logical/upload_service.rb @@ -130,21 +130,23 @@ class UploadService def self.generate_resizes(file, upload) if upload.is_video? video = FFMPEG::Movie.new(file.path) + crop_file = generate_video_preview_for(video, Danbooru.config.small_image_width, Danbooru.config.small_image_width) preview_file = generate_video_preview_for(video, Danbooru.config.small_image_width, Danbooru.config.small_image_width) elsif upload.is_ugoira? preview_file = PixivUgoiraConverter.generate_preview(file) + crop_file = PixivUgoiraConverter.generate_crop(file) sample_file = PixivUgoiraConverter.generate_webm(file, upload.context["ugoira"]["frame_data"]) elsif upload.is_image? preview_file = DanbooruImageResizer.resize(file, Danbooru.config.small_image_width, Danbooru.config.small_image_width, 85) - + crop_file = DanbooruImageResizer.crop(file, Danbooru.config.small_image_width, 85) if upload.image_width > Danbooru.config.large_image_width sample_file = DanbooruImageResizer.resize(file, Danbooru.config.large_image_width, upload.image_height, 90) end end - [preview_file, sample_file] + [preview_file, crop_file, sample_file] end def self.generate_video_preview_for(video, width, height) @@ -155,7 +157,7 @@ class UploadService width = (height * dimension_ratio).to_i end - output_file = Tempfile.new(binmode: true) + output_file = Tempfile.new(["video-preview", ".jpg"], binmode: true) video.screenshot(output_file.path, {:seek_time => 0, :resolution => "#{width}x#{height}"}) output_file end @@ -178,14 +180,16 @@ class UploadService upload.tag_string = "#{upload.tag_string} #{Utils.automatic_tags(upload, file)}" - preview_file, sample_file = Utils.generate_resizes(file, upload) + preview_file, crop_file, sample_file = Utils.generate_resizes(file, upload) begin Utils.distribute_files(file, upload, :original) Utils.distribute_files(sample_file, upload, :large) if sample_file.present? Utils.distribute_files(preview_file, upload, :preview) if preview_file.present? + Utils.distribute_files(crop_file, upload, :crop) if crop_file.present? ensure preview_file.try(:close!) + crop_file.try(:close!) sample_file.try(:close!) end @@ -583,13 +587,13 @@ class UploadService ) end - notify_cropper(@post) if ImageCropper.enabled? upload.update(status: "completed", post_id: @post.id) @post end def convert_to_post(upload) Post.new.tap do |p| + p.has_cropped = true p.tag_string = upload.tag_string p.md5 = upload.md5 p.file_ext = upload.file_ext @@ -607,8 +611,4 @@ class UploadService end end end - - def notify_cropper(post) - # ImageCropper.notify(post) - end end diff --git a/app/models/post.rb b/app/models/post.rb index a4b56dc88..006426973 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -173,6 +173,10 @@ class Post < ApplicationRecord storage_manager.file_path(md5, file_ext, :preview) end + def crop_file_url + storage_manager.file_url(self, :crop) + end + def open_graph_image_url if is_image? if has_large? diff --git a/app/models/user.rb b/app/models/user.rb index 09b90ada0..510265637 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -92,6 +92,8 @@ class User < ApplicationRecord has_many :post_approvals, :dependent => :destroy has_many :post_disapprovals, :dependent => :destroy has_many :post_votes + has_many :post_archives + has_many :note_versions has_many :bans, -> {order("bans.id desc")} has_one :recent_ban, -> {order("bans.id desc")}, :class_name => "Ban" diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index 7a532cea8..2704f71b2 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -34,14 +34,16 @@ class PostPresenter < Presenter end html << %{} - if options[:show_cropped] && post.has_cropped? - src = post.cropped_file_url + if CurrentUser.id == 1 && options[:show_cropped] && post.has_cropped? && !CurrentUser.user.disable_cropped_thumbnails? + src = post.crop_file_url + imgClass = "cropped" else src = post.preview_file_url + imgClass = nil end tooltip = "#{post.tag_string} rating:#{post.rating} score:#{post.score}" - html << %{#{h(post.tag_string)}} + html << %{#{h(post.tag_string)}} html << %{} if options[:pool] @@ -71,7 +73,7 @@ class PostPresenter < Presenter def self.preview_class(post, description = nil, options = {}) klass = "post-preview" - klass << " large-cropped" if post.has_cropped? && options[:show_cropped] + # klass << " large-cropped" if post.has_cropped? && options[:show_cropped] klass << " pooled" if description klass << " post-status-pending" if post.is_pending? klass << " post-status-flagged" if post.is_flagged? diff --git a/app/presenters/post_set_presenters/base.rb b/app/presenters/post_set_presenters/base.rb index 402844d12..060779478 100644 --- a/app/presenters/post_set_presenters/base.rb +++ b/app/presenters/post_set_presenters/base.rb @@ -12,7 +12,7 @@ module PostSetPresenters end posts.each do |post| - html << PostPresenter.preview(post, options.merge(:tags => @post_set.tag_string, :raw => @post_set.raw)) + html << PostPresenter.preview(post, options.merge(:show_cropped => true, :tags => @post_set.tag_string, :raw => @post_set.raw)) html << "\n" end diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index b51132aaa..c5b836dd0 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -88,7 +88,12 @@ <%= render "news_updates/listing" %>

<%= link_to Danbooru.config.app_name, "/" %>

- + +
+ + +
+