From e5ba6d4afc6020f48bad8db62d4008e291d4072d Mon Sep 17 00:00:00 2001 From: evazion Date: Tue, 30 Nov 2021 04:17:17 -0600 Subject: [PATCH] MediaFile: fix thumbnail dimension calculation. Calculate the dimensions of thumbnails ourselves instead of letting libvips calculate them for us. This way we know the exact size of thumbnails, so we can set the right width and height for tags. If we let libvips calculate thumbnail sizes for us, then we can't predict the exact size of thumbnails, because sometimes libvips rounds numbers differently than us. --- app/components/post_preview_component.rb | 15 ++++--- .../post_preview_component.html.erb | 2 +- app/logical/media_file.rb | 13 ++++++ app/logical/media_file/image.rb | 5 ++- app/models/media_asset.rb | 44 ++++++++++++++++--- 5 files changed, 65 insertions(+), 14 deletions(-) diff --git a/app/components/post_preview_component.rb b/app/components/post_preview_component.rb index 8c9e7369a..3b9ca1784 100644 --- a/app/components/post_preview_component.rb +++ b/app/components/post_preview_component.rb @@ -42,13 +42,16 @@ class PostPreviewComponent < ApplicationComponent end end - def preview_dimensions - downscale_ratio = Danbooru.config.small_image_width.to_f / [post.image_width, post.image_height].max + def preview_width + variant.width + end - { - width: [(downscale_ratio * post.image_width).floor, post.image_width].min, - height: [(downscale_ratio * post.image_height).floor, post.image_height].min, - } + def preview_height + variant.height + end + + def variant + @variant ||= media_asset.variant(:preview) end def tooltip diff --git a/app/components/post_preview_component/post_preview_component.html.erb b/app/components/post_preview_component/post_preview_component.html.erb index 49d90e5c7..8a82b801e 100644 --- a/app/components/post_preview_component/post_preview_component.html.erb +++ b/app/components/post_preview_component/post_preview_component.html.erb @@ -16,7 +16,7 @@ <%= tag.picture do -%> <%= tag.source media: "(max-width: 660px)", srcset: cropped_url -%> <%= tag.source media: "(min-width: 660px)", srcset: post.preview_file_url -%> - <%= tag.img class: "has-cropped-#{post.has_cropped?}", src: post.preview_file_url, style: "min-width: #{preview_dimensions[:width]}px; min-height: #{preview_dimensions[:height]}px;", title: tooltip, alt: "post ##{post.id}", crossorigin: "anonymous" -%> + <%= tag.img class: "has-cropped-#{post.has_cropped?}", src: post.preview_file_url, style: "min-width: #{preview_width}px; min-height: #{preview_height}px;", title: tooltip, alt: "post ##{post.id}", crossorigin: "anonymous" -%> <% end -%> <% end -%> <% if pool -%> diff --git a/app/logical/media_file.rb b/app/logical/media_file.rb index ac4dd8bcd..6b3ea53e5 100644 --- a/app/logical/media_file.rb +++ b/app/logical/media_file.rb @@ -202,5 +202,18 @@ class MediaFile }.stringify_keys end + # Scale `width` and `height` to fit within `max_width` and `max_height`. + def self.scale_dimensions(width, height, max_width, max_height) + max_width ||= Float::INFINITY + max_height ||= Float::INFINITY + + if width <= max_width && height <= max_height + [width, height] + else + scale = [max_width.to_f / width.to_f, max_height.to_f / height.to_f].min + [(width * scale).round.to_i, (height * scale).round.to_i] + end + end + memoize :file_ext, :file_size, :md5, :metadata end diff --git a/app/logical/media_file/image.rb b/app/logical/media_file/image.rb index 5e137f7e2..3535f43ab 100644 --- a/app/logical/media_file/image.rb +++ b/app/logical/media_file/image.rb @@ -72,8 +72,9 @@ class MediaFile::Image < MediaFile MediaFile::Image.new(output_file) end - def preview(max_width, max_height) - resize(max_width, max_height, size: :down) + def preview(max_width, max_height, **options) + w, h = MediaFile.scale_dimensions(width, height, max_width, max_height) + resize(w, h, size: :force, **options) end def crop(max_width, max_height) diff --git a/app/models/media_asset.rb b/app/models/media_asset.rb index 8336a5e5f..0620786dc 100644 --- a/app/models/media_asset.rb +++ b/app/models/media_asset.rb @@ -25,12 +25,14 @@ class MediaAsset < ApplicationRecord validates :md5, uniqueness: { conditions: -> { where(status: [:processing, :active]) } } class Variant + extend Memoist + attr_reader :media_asset, :variant delegate :md5, :storage_service, :backup_storage_service, to: :media_asset def initialize(media_asset, variant) @media_asset = media_asset - @variant = variant + @variant = variant.to_sym raise ArgumentError, "asset doesn't have #{variant} variant" unless Variant.exists?(media_asset, variant) end @@ -55,13 +57,13 @@ class MediaAsset < ApplicationRecord def convert_file(media_file) case variant in :preview - media_file.preview(Danbooru.config.small_image_width, Danbooru.config.small_image_width) + media_file.preview(width, height) in :crop - media_file.crop(Danbooru.config.small_image_width, Danbooru.config.small_image_width) + media_file.crop(width, height) in :sample if media_asset.is_ugoira? media_file.convert - in :sample if media_asset.is_static_image? && media_asset.image_width > Danbooru.config.large_image_width - media_file.preview(Danbooru.config.large_image_width, media_asset.image_height) + in :sample if media_asset.is_static_image? + media_file.preview(width, height) in :original media_file end @@ -105,6 +107,36 @@ class MediaAsset < ApplicationRecord end end + def max_dimensions + case variant + when :preview + [150, 150] + when :crop + [150, 150] + when :sample + [850, nil] + when :original + [nil, nil] + end + end + + def dimensions + case variant + when :crop + max_dimensions + else + MediaFile.scale_dimensions(media_asset.image_width, media_asset.image_height, max_dimensions[0], max_dimensions[1]) + end + end + + def width + dimensions[0] + end + + def height + dimensions[1] + end + def self.exists?(media_asset, variant) case variant when :preview @@ -117,6 +149,8 @@ class MediaAsset < ApplicationRecord true end end + + memoize :file_name, :file_ext, :max_dimensions, :dimensions end concerning :SearchMethods do