diff --git a/app/javascript/src/styles/specific/posts.scss b/app/javascript/src/styles/specific/posts.scss index fba8ce4d4..628d2a10b 100644 --- a/app/javascript/src/styles/specific/posts.scss +++ b/app/javascript/src/styles/specific/posts.scss @@ -159,6 +159,7 @@ div#c-posts { .image-container { margin: 1em 0 0.5em; + image-orientation: from-image; &.danbirthday::before { content: ""; diff --git a/app/logical/media_file/image.rb b/app/logical/media_file/image.rb index 1d3c563c3..9d77c3e33 100644 --- a/app/logical/media_file/image.rb +++ b/app/logical/media_file/image.rb @@ -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) diff --git a/app/models/post.rb b/app/models/post.rb index 4b70f75fb..b4d13b9c2 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -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 diff --git a/test/files/test-rotation-180.jpg b/test/files/test-rotation-180.jpg new file mode 100644 index 000000000..63cd9c50b Binary files /dev/null and b/test/files/test-rotation-180.jpg differ diff --git a/test/files/test-rotation-270cw.jpg b/test/files/test-rotation-270cw.jpg new file mode 100644 index 000000000..7939638e8 Binary files /dev/null and b/test/files/test-rotation-270cw.jpg differ diff --git a/test/functional/post_regenerations_controller_test.rb b/test/functional/post_regenerations_controller_test.rb index 75a34670f..443fbe047 100644 --- a/test/functional/post_regenerations_controller_test.rb +++ b/test/functional/post_regenerations_controller_test.rb @@ -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 diff --git a/test/unit/media_file_test.rb b/test/unit/media_file_test.rb index 021df68cb..6d0650332 100644 --- a/test/unit/media_file_test.rb +++ b/test/unit/media_file_test.rb @@ -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