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 <img> 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.
This commit is contained in:
evazion
2021-11-30 04:17:17 -06:00
parent c2e6202da6
commit e5ba6d4afc
5 changed files with 65 additions and 14 deletions

View File

@@ -42,13 +42,16 @@ class PostPreviewComponent < ApplicationComponent
end end
end end
def preview_dimensions def preview_width
downscale_ratio = Danbooru.config.small_image_width.to_f / [post.image_width, post.image_height].max variant.width
end
{ def preview_height
width: [(downscale_ratio * post.image_width).floor, post.image_width].min, variant.height
height: [(downscale_ratio * post.image_height).floor, post.image_height].min, end
}
def variant
@variant ||= media_asset.variant(:preview)
end end
def tooltip def tooltip

View File

@@ -16,7 +16,7 @@
<%= tag.picture do -%> <%= tag.picture do -%>
<%= tag.source media: "(max-width: 660px)", srcset: cropped_url -%> <%= tag.source media: "(max-width: 660px)", srcset: cropped_url -%>
<%= tag.source media: "(min-width: 660px)", srcset: post.preview_file_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 -%>
<% end -%> <% end -%>
<% if pool -%> <% if pool -%>

View File

@@ -202,5 +202,18 @@ class MediaFile
}.stringify_keys }.stringify_keys
end 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 memoize :file_ext, :file_size, :md5, :metadata
end end

View File

@@ -72,8 +72,9 @@ class MediaFile::Image < MediaFile
MediaFile::Image.new(output_file) MediaFile::Image.new(output_file)
end end
def preview(max_width, max_height) def preview(max_width, max_height, **options)
resize(max_width, max_height, size: :down) w, h = MediaFile.scale_dimensions(width, height, max_width, max_height)
resize(w, h, size: :force, **options)
end end
def crop(max_width, max_height) def crop(max_width, max_height)

View File

@@ -25,12 +25,14 @@ class MediaAsset < ApplicationRecord
validates :md5, uniqueness: { conditions: -> { where(status: [:processing, :active]) } } validates :md5, uniqueness: { conditions: -> { where(status: [:processing, :active]) } }
class Variant class Variant
extend Memoist
attr_reader :media_asset, :variant attr_reader :media_asset, :variant
delegate :md5, :storage_service, :backup_storage_service, to: :media_asset delegate :md5, :storage_service, :backup_storage_service, to: :media_asset
def initialize(media_asset, variant) def initialize(media_asset, variant)
@media_asset = media_asset @media_asset = media_asset
@variant = variant @variant = variant.to_sym
raise ArgumentError, "asset doesn't have #{variant} variant" unless Variant.exists?(media_asset, variant) raise ArgumentError, "asset doesn't have #{variant} variant" unless Variant.exists?(media_asset, variant)
end end
@@ -55,13 +57,13 @@ class MediaAsset < ApplicationRecord
def convert_file(media_file) def convert_file(media_file)
case variant case variant
in :preview in :preview
media_file.preview(Danbooru.config.small_image_width, Danbooru.config.small_image_width) media_file.preview(width, height)
in :crop 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? in :sample if media_asset.is_ugoira?
media_file.convert media_file.convert
in :sample if media_asset.is_static_image? && media_asset.image_width > Danbooru.config.large_image_width in :sample if media_asset.is_static_image?
media_file.preview(Danbooru.config.large_image_width, media_asset.image_height) media_file.preview(width, height)
in :original in :original
media_file media_file
end end
@@ -105,6 +107,36 @@ class MediaAsset < ApplicationRecord
end end
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) def self.exists?(media_asset, variant)
case variant case variant
when :preview when :preview
@@ -117,6 +149,8 @@ class MediaAsset < ApplicationRecord
true true
end end
end end
memoize :file_name, :file_ext, :max_dimensions, :dimensions
end end
concerning :SearchMethods do concerning :SearchMethods do