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:
@@ -159,6 +159,7 @@ div#c-posts {
|
||||
|
||||
.image-container {
|
||||
margin: 1em 0 0.5em;
|
||||
image-orientation: from-image;
|
||||
|
||||
&.danbirthday::before {
|
||||
content: "";
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
BIN
test/files/test-rotation-180.jpg
Normal file
BIN
test/files/test-rotation-180.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
test/files/test-rotation-270cw.jpg
Normal file
BIN
test/files/test-rotation-270cw.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user