Fix #3692: Rotate pictures based on metadata

Rotate the image based on the EXIF orientation flag when generating
thumbnails and samples.

Also fix the width and height to be calculated correctly for rotated
images. Vips gives us the unrotated width and height of the image; we
have to detect whether the image is rotated and swap the width and
height manually to correct them. For example, if an image with the
"Rotate 90 CW" flag is 100x500 before rotation, then after rotation it's
500x100. This should fix #4883 (Exif rotation breaks Javascript fit-to-window)

We also have to fix it so that regenerating a post updates the width and
height of the post, in the event that it's a rotated image.

Finally we set `image-orientation: from-image;` even though it's
probably not necessary.
This commit is contained in:
evazion
2021-09-22 10:52:17 -05:00
parent 9b6c429d4c
commit b378785582
7 changed files with 85 additions and 3 deletions

View File

@@ -159,6 +159,7 @@ div#c-posts {
.image-container {
margin: 1em 0 0.5em;
image-orientation: from-image;
&.danbirthday::before {
content: "";

View File

@@ -7,15 +7,27 @@ class MediaFile::Image < MediaFile
JPEG_OPTIONS = { Q: 90, background: 255, strip: true, interlace: true, optimize_coding: true }
# http://jcupitt.github.io/libvips/API/current/libvips-resample.html#vips-thumbnail
THUMBNAIL_OPTIONS = { size: :down, linear: false, no_rotate: true }
CROP_OPTIONS = { crop: :attention, linear: false, no_rotate: true }
THUMBNAIL_OPTIONS = { size: :down, linear: false }
CROP_OPTIONS = { crop: :attention, linear: false }
def dimensions
image.size
[width, height]
rescue Vips::Error
[0, 0]
end
def width
is_rotated? ? image.height : image.width
rescue Vips::Error
0
end
def height
is_rotated? ? image.width : image.height
rescue Vips::Error
0
end
def is_corrupt?
image.stats
false
@@ -69,6 +81,11 @@ class MediaFile::Image < MediaFile
file_ext == :png && metadata.fetch("PNG:AnimationFrames", 1) > 1
end
# https://exiftool.org/TagNames/EXIF.html
def is_rotated?
metadata["IFD0:Orientation"].in?(["Rotate 90 CW", "Rotate 270 CW"])
end
# @return [Vips::Image] the Vips image object for the file
def image
Vips::Image.new_from_file(file.path, fail: true)

View File

@@ -1348,6 +1348,20 @@ class Post < ApplicationRecord
media_file = MediaFile.open(file, frame_data: pixiv_ugoira_frame_data&.data.to_a)
UploadService::Utils.process_resizes(self, nil, id, media_file: media_file)
update!(
image_width: media_file.width,
image_height: media_file.height,
file_size: media_file.file_size,
file_ext: media_file.file_ext,
)
media_asset.update!(
image_width: media_file.width,
image_height: media_file.height,
file_size: media_file.file_size,
file_ext: media_file.file_ext,
)
purge_cached_urls!
update_iqdb

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -58,6 +58,20 @@ class PostRegenerationsControllerTest < ActionDispatch::IntegrationTest
assert_equal("post_regenerate", ModAction.last.category)
assert_equal("<@#{@mod.name}> regenerated image samples for post ##{@post.id}", ModAction.last.description)
end
should "fix the width and height of exif-rotated images" do
@upload = assert_successful_upload("test/files/test-rotation-90cw.jpg", user: @mod)
@post = @upload.post
post_auth post_regenerations_path, @mod, params: { post_id: @post.id }
perform_enqueued_jobs
@post.reload
assert_equal(96, @post.image_width)
assert_equal(128, @post.image_height)
assert_equal(96, @post.media_asset.image_width)
assert_equal(128, @post.media_asset.image_height)
end
end
end
end

View File

@@ -323,4 +323,40 @@ class MediaFileTest < ActiveSupport::TestCase
assert_equal([120, 150], @preview.dimensions)
end
end
context "an image that is rotated 90 degrees clockwise" do
should "have the correct dimensions" do
@file = MediaFile.open("test/files/test-rotation-90cw.jpg")
assert_equal([96, 128], @file.dimensions)
end
should "generate a rotated thumbnail" do
@file = MediaFile.open("test/files/test-rotation-90cw.jpg")
assert_equal([48, 64], @file.preview(64, 64).dimensions)
end
end
context "an image that is rotated 270 degrees clockwise" do
should "have the correct dimensions" do
@file = MediaFile.open("test/files/test-rotation-270cw.jpg")
assert_equal([100, 66], @file.dimensions)
end
should "generate a rotated thumbnail" do
@file = MediaFile.open("test/files/test-rotation-270cw.jpg")
assert_equal([50, 33], @file.preview(50, 50).dimensions)
end
end
context "an image that is rotated 180 degrees" do
should "have the correct dimensions" do
@file = MediaFile.open("test/files/test-rotation-180.jpg")
assert_equal([66, 100], @file.dimensions)
end
should "generate a rotated thumbnail" do
@file = MediaFile.open("test/files/test-rotation-180.jpg")
assert_equal([33, 50], @file.preview(50, 50).dimensions)
end
end
end