Files
danbooru/app/logical/media_file/image.rb
evazion b378785582 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.
2021-09-22 11:12:50 -05:00

96 lines
2.4 KiB
Ruby

# A MediaFile for a JPEG, PNG, or GIF file. Uses libvips for resizing images.
#
# @see https://github.com/libvips/ruby-vips
# @see https://libvips.github.io/libvips/API/current
class MediaFile::Image < MediaFile
# http://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave
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 }
CROP_OPTIONS = { crop: :attention, linear: false }
def dimensions
[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
rescue Vips::Error
true
end
def is_animated?
is_animated_gif? || is_animated_png?
end
def channels
image.bands
end
def colorspace
image.interpretation
end
# @see https://github.com/jcupitt/libvips/wiki/HOWTO----Image-shrinking
# @see http://jcupitt.github.io/libvips/API/current/Using-vipsthumbnail.md.html
def preview(width, height)
output_file = Tempfile.new(["image-preview", ".jpg"])
resized_image = preview_frame.image.thumbnail_image(width, height: height, **THUMBNAIL_OPTIONS)
resized_image.jpegsave(output_file.path, **JPEG_OPTIONS)
MediaFile::Image.new(output_file)
end
def crop(width, height)
output_file = Tempfile.new(["image-crop", ".jpg"])
resized_image = preview_frame.image.thumbnail_image(width, height: height, **CROP_OPTIONS)
resized_image.jpegsave(output_file.path, **JPEG_OPTIONS)
MediaFile::Image.new(output_file)
end
def preview_frame
if is_animated?
FFmpeg.new(file).smart_video_preview
else
self
end
end
def is_animated_gif?
file_ext == :gif && image.get("n-pages") > 1
end
def is_animated_png?
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)
end
memoize :image, :dimensions, :is_corrupt?, :is_animated_gif?, :is_animated_png?
end