LibGfx+LibWeb: Move EXIF orientation to rendering

This commit is contained in:
Tuur Martens 2025-03-22 08:21:58 +01:00
parent ca200142e9
commit 26d8e978d8
28 changed files with 403 additions and 94 deletions

View file

@ -20,6 +20,26 @@ struct BackingStore {
size_t size_in_bytes { 0 };
};
IntRect Bitmap::rect() const
{
return { {}, size() };
}
IntSize Bitmap::size() const
{
return exif_oriented_size(m_size, m_exif_orientation);
}
int Bitmap::width() const
{
return size().width();
}
int Bitmap::height() const
{
return size().height();
}
size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
{
size_t element_size;
@ -49,19 +69,19 @@ static bool size_would_overflow(BitmapFormat format, IntSize size)
return Checked<size_t>::multiplication_would_overflow(pitch, size.height());
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, IntSize size)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, IntSize size, ExifOrientation orientation)
{
// For backwards compatibility, premultiplied alpha is assumed
return create(format, AlphaType::Premultiplied, size);
return create(format, AlphaType::Premultiplied, size, orientation);
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, AlphaType alpha_type, IntSize size)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, AlphaType alpha_type, IntSize size, ExifOrientation orientation)
{
auto backing_store = TRY(Bitmap::allocate_backing_store(format, size));
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, size, backing_store));
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, size, backing_store, orientation));
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, AlphaType alpha_type, IntSize size)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, AlphaType alpha_type, IntSize size, ExifOrientation orientation)
{
if (size_would_overflow(format, size))
return Error::from_string_literal("Gfx::Bitmap::create_shareable size overflow");
@ -70,16 +90,17 @@ ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, Alp
auto const data_size = size_in_bytes(pitch, size.height());
auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(data_size, PAGE_SIZE)));
auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(format, alpha_type, buffer, size));
auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(format, alpha_type, buffer, size, orientation));
return bitmap;
}
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, IntSize size, BackingStore const& backing_store)
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, IntSize size, BackingStore const& backing_store, ExifOrientation orientation)
: m_size(size)
, m_data(backing_store.data)
, m_pitch(backing_store.pitch)
, m_format(format)
, m_alpha_type(alpha_type)
, m_exif_orientation(orientation)
{
VERIFY(!m_size.is_empty());
VERIFY(!size_would_overflow(format, size));
@ -90,41 +111,43 @@ Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, IntSize size, BackingS
};
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_wrapper(BitmapFormat format, AlphaType alpha_type, IntSize size, size_t pitch, void* data, Function<void()>&& destruction_callback)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_wrapper(BitmapFormat format, AlphaType alpha_type, IntSize size, size_t pitch, void* data, Function<void()>&& destruction_callback, ExifOrientation orientation)
{
if (size_would_overflow(format, size))
return Error::from_string_literal("Gfx::Bitmap::create_wrapper size overflow");
return adopt_ref(*new Bitmap(format, alpha_type, size, pitch, data, move(destruction_callback)));
return adopt_ref(*new Bitmap(format, alpha_type, size, pitch, data, move(destruction_callback), orientation));
}
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, IntSize size, size_t pitch, void* data, Function<void()>&& destruction_callback)
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, IntSize size, size_t pitch, void* data, Function<void()>&& destruction_callback, ExifOrientation orientation)
: m_size(size)
, m_data(data)
, m_pitch(pitch)
, m_format(format)
, m_alpha_type(alpha_type)
, m_destruction_callback(move(destruction_callback))
, m_exif_orientation(orientation)
{
VERIFY(pitch >= minimum_pitch(size.width(), format));
VERIFY(!size_would_overflow(format, size));
// FIXME: assert that `data` is actually long enough!
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size, ExifOrientation orientation)
{
if (size_would_overflow(format, size))
return Error::from_string_literal("Gfx::Bitmap::create_with_anonymous_buffer size overflow");
return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, move(buffer), size));
return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, move(buffer), size, orientation));
}
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size)
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size, ExifOrientation orientation)
: m_size(size)
, m_data(buffer.data<void>())
, m_pitch(minimum_pitch(size.width(), format))
, m_format(format)
, m_alpha_type(alpha_type)
, m_buffer(move(buffer))
, m_exif_orientation(orientation)
{
VERIFY(!size_would_overflow(format, size));
}

View file

@ -12,6 +12,7 @@
#include <LibCore/AnonymousBuffer.h>
#include <LibGfx/Color.h>
#include <LibGfx/Forward.h>
#include <LibGfx/ImageOrientation.h>
#include <LibGfx/Rect.h>
namespace Gfx {
@ -64,11 +65,11 @@ struct BackingStore;
class Bitmap : public RefCounted<Bitmap> {
public:
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, AlphaType, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_shareable(BitmapFormat, AlphaType, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_wrapper(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback = {});
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_anonymous_buffer(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, IntSize, ExifOrientation = ExifOrientation::Default);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, AlphaType, IntSize, ExifOrientation = ExifOrientation::Default);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_shareable(BitmapFormat, AlphaType, IntSize, ExifOrientation = ExifOrientation::Default);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_wrapper(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback = {}, ExifOrientation = ExifOrientation::Default);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_anonymous_buffer(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize, ExifOrientation = ExifOrientation::Default);
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> clone() const;
@ -102,10 +103,11 @@ public:
[[nodiscard]] ARGB32 const* end() const;
[[nodiscard]] size_t data_size() const;
[[nodiscard]] IntRect rect() const { return { {}, m_size }; }
[[nodiscard]] IntSize size() const { return m_size; }
[[nodiscard]] int width() const { return m_size.width(); }
[[nodiscard]] int height() const { return m_size.height(); }
[[nodiscard]] IntRect rect() const;
[[nodiscard]] IntSize size() const;
[[nodiscard]] int width() const;
[[nodiscard]] int height() const;
[[nodiscard]] ExifOrientation exif_orientation() const { return m_exif_orientation; }
[[nodiscard]] size_t pitch() const { return m_pitch; }
@ -148,9 +150,9 @@ public:
void set_alpha_type_destructive(AlphaType);
private:
Bitmap(BitmapFormat, AlphaType, IntSize, BackingStore const&);
Bitmap(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback);
Bitmap(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize);
Bitmap(BitmapFormat, AlphaType, IntSize, BackingStore const&, ExifOrientation);
Bitmap(BitmapFormat, AlphaType, IntSize, size_t pitch, void*, Function<void()>&& destruction_callback, ExifOrientation);
Bitmap(BitmapFormat, AlphaType, Core::AnonymousBuffer, IntSize, ExifOrientation);
static ErrorOr<BackingStore> allocate_backing_store(BitmapFormat format, IntSize size);
@ -161,6 +163,7 @@ private:
AlphaType m_alpha_type { AlphaType::Premultiplied };
Core::AnonymousBuffer m_buffer;
Function<void()> m_destruction_callback;
ExifOrientation m_exif_orientation;
};
ALWAYS_INLINE u8* Bitmap::unchecked_scanline_u8(int y)

View file

@ -16,7 +16,7 @@ namespace Gfx {
static BitmapMetadata get_metadata(Bitmap const& bitmap)
{
return BitmapMetadata { .format = bitmap.format(), .alpha_type = bitmap.alpha_type(), .size = bitmap.size(), .size_in_bytes = bitmap.size_in_bytes() };
return BitmapMetadata { .format = bitmap.format(), .alpha_type = bitmap.alpha_type(), .size = bitmap.size(), .exif_orientation = bitmap.exif_orientation(), .size_in_bytes = bitmap.size_in_bytes() };
}
}
@ -30,6 +30,7 @@ ErrorOr<void> encode(Encoder& encoder, Gfx::BitmapMetadata const& metadata)
TRY(encoder.encode(static_cast<u32>(metadata.alpha_type)));
TRY(encoder.encode(metadata.size_in_bytes));
TRY(encoder.encode(metadata.size));
TRY(encoder.encode(static_cast<u32>(metadata.exif_orientation)));
return {};
}
@ -49,8 +50,12 @@ ErrorOr<Gfx::BitmapMetadata> decode(Decoder& decoder)
auto size_in_bytes = TRY(decoder.decode<size_t>());
auto size = TRY(decoder.decode<Gfx::IntSize>());
auto raw_exif_orientation = TRY(decoder.decode<u32>());
if (!Gfx::is_valid_exif_orientation(raw_exif_orientation))
return Error::from_string_literal("IPC: Invalid Gfx::BitmapSequence exif orientation");
auto exif_orientation = static_cast<Gfx::ExifOrientation>(raw_exif_orientation);
return Gfx::BitmapMetadata { format, alpha_type, size, size_in_bytes };
return Gfx::BitmapMetadata { format, alpha_type, size, exif_orientation, size_in_bytes };
}
template<>
@ -135,7 +140,7 @@ ErrorOr<Gfx::BitmapSequence> decode(Decoder& decoder)
bytes_read += size_in_bytes;
bitmap = TRY(Gfx::Bitmap::create_with_anonymous_buffer(metadata.format, metadata.alpha_type, move(buffer), metadata.size));
bitmap = TRY(Gfx::Bitmap::create_with_anonymous_buffer(metadata.format, metadata.alpha_type, move(buffer), metadata.size, metadata.exif_orientation));
}
bitmaps.append(bitmap);

View file

@ -8,6 +8,7 @@
#include <AK/RefPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageOrientation.h>
#include <LibGfx/Size.h>
#include <LibIPC/Forward.h>
@ -22,6 +23,7 @@ struct BitmapMetadata {
Gfx::BitmapFormat format;
Gfx::AlphaType alpha_type;
Gfx::IntSize size;
Gfx::ExifOrientation exif_orientation;
size_t size_in_bytes;
};

View file

@ -58,6 +58,7 @@ set(SOURCES
Triangle.cpp
VectorGraphic.cpp
SkiaBackendContext.cpp
ImageOrientation.cpp
)
set(SWIFT_EXCLUDE_HEADERS

View file

@ -10,6 +10,7 @@
#include <LibGfx/ImageFormats/PNGLoader.h>
#include <LibGfx/ImageFormats/TIFFLoader.h>
#include <LibGfx/ImageFormats/TIFFMetadata.h>
#include <LibGfx/ImageOrientation.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/Painter.h>
#include <png.h>
@ -35,7 +36,6 @@ struct PNGLoadingContext {
OwnPtr<ExifMetadata> exif_metadata;
ErrorOr<size_t> read_frames(png_structp, png_infop);
ErrorOr<void> apply_exif_orientation();
ErrorOr<void> read_all_frames()
{
@ -48,8 +48,6 @@ struct PNGLoadingContext {
frame_count = TRY(read_frames(png_ptr, info_ptr));
if (exif_metadata)
TRY(apply_exif_orientation());
return {};
}
};
@ -221,36 +219,13 @@ ErrorOr<void> PNGImageDecoderPlugin::initialize()
return {};
}
ErrorOr<void> PNGLoadingContext::apply_exif_orientation()
{
auto orientation = exif_metadata->orientation().value_or(TIFF::Orientation::Default);
if (orientation == TIFF::Orientation::Default)
return {};
for (auto& img_frame_descriptor : frame_descriptors) {
auto& img = img_frame_descriptor.image;
auto oriented_bmp = TRY(ExifOrientedBitmap::create(orientation, img->size(), img->format()));
for (int y = 0; y < img->size().height(); ++y) {
for (int x = 0; x < img->size().width(); ++x) {
auto pixel = img->get_pixel(x, y);
oriented_bmp.set_pixel(x, y, pixel.value());
}
}
img_frame_descriptor.image = oriented_bmp.bitmap();
}
size = ExifOrientedBitmap::oriented_size(size, orientation);
return {};
}
ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop info_ptr)
{
auto exif_orientation = exif_metadata ? static_cast<ExifOrientation>(exif_metadata->orientation().value()) : ExifOrientation::Default;
Vector<u8*> row_pointers;
auto decode_frame = [&](IntSize frame_size) -> ErrorOr<NonnullRefPtr<Bitmap>> {
auto frame_bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, AlphaType::Unpremultiplied, frame_size));
auto frame_bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, AlphaType::Unpremultiplied, frame_size, exif_orientation));
row_pointers.resize_and_keep_capacity(frame_size.height());
for (auto i = 0; i < frame_size.height(); ++i)
@ -259,7 +234,7 @@ ErrorOr<size_t> PNGLoadingContext::read_frames(png_structp png_ptr, png_infop in
png_read_image(png_ptr, row_pointers.data());
return frame_bitmap;
};
if (png_get_acTL(png_ptr, info_ptr, &frame_count, &loop_count)) {
// acTL chunk present: This is an APNG.
png_set_acTL(png_ptr, info_ptr, frame_count, loop_count);

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2025, Tuur Martens <tuurmartens4@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/ImageOrientation.h>
namespace Gfx {
bool is_valid_exif_orientation(u32 orientation)
{
switch (static_cast<Gfx::ExifOrientation>(orientation)) {
case Gfx::ExifOrientation::Default:
case Gfx::ExifOrientation::FlipHorizontally:
case Gfx::ExifOrientation::Rotate180:
case Gfx::ExifOrientation::FlipVertically:
case Gfx::ExifOrientation::Rotate90ClockwiseThenFlipHorizontally:
case Gfx::ExifOrientation::Rotate90Clockwise:
case Gfx::ExifOrientation::FlipHorizontallyThenRotate90Clockwise:
case Gfx::ExifOrientation::Rotate90CounterClockwise:
return true;
default:
return false;
}
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2025, Tuur Martens <tuurmartens4@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Rect.h>
#include <cmath>
namespace Gfx {
enum class ExifOrientation : u8 {
Default = 1,
FlipHorizontally = 2,
Rotate180 = 3,
FlipVertically = 4,
Rotate90ClockwiseThenFlipHorizontally = 5,
Rotate90Clockwise = 6,
FlipHorizontallyThenRotate90Clockwise = 7,
Rotate90CounterClockwise = 8,
};
[[nodiscard]]
bool is_valid_exif_orientation(u32 orientation);
template<typename T>
[[nodiscard]]
Gfx::AffineTransform compute_exif_orientation_matrix(Gfx::ExifOrientation orientation, Gfx::Rect<T> const& dst_rect)
{
Gfx::AffineTransform matrix;
switch (orientation) {
case Gfx::ExifOrientation::Default:
return matrix;
case Gfx::ExifOrientation::FlipHorizontally:
matrix.set_translation(dst_rect.width() / 2, 0);
matrix.set_scale(-1, 1);
matrix.translate(-dst_rect.width() / 2.f, 0);
break;
case Gfx::ExifOrientation::Rotate180:
matrix.set_translation(dst_rect.width(), dst_rect.height());
matrix.rotate_radians(M_PI);
break;
case Gfx::ExifOrientation::FlipVertically:
matrix.set_translation(0, dst_rect.height() / 2);
matrix.set_scale(1, -1);
matrix.translate(0, -dst_rect.height() / 2);
break;
case Gfx::ExifOrientation::Rotate90ClockwiseThenFlipHorizontally:
matrix.set_translation(dst_rect.height(), 0);
matrix.rotate_radians(-M_PI / 2.);
matrix.translate(0, -dst_rect.height());
matrix.scale(-1, 1);
break;
case Gfx::ExifOrientation::Rotate90Clockwise:
matrix.set_translation(dst_rect.width(), 0);
matrix.rotate_radians(M_PI / 2.);
break;
case Gfx::ExifOrientation::FlipHorizontallyThenRotate90Clockwise:
// We translate by the dst_rect.height(), which will be the new dst_rect.width() of the image.
matrix.set_translation(dst_rect.width(), 0);
matrix.rotate_radians(M_PI / 2.);
// We translate by the old dst_rect.height() to move the image back to the origin.
matrix.translate(dst_rect.height(), 0);
matrix.scale(-1, 1);
break;
case Gfx::ExifOrientation::Rotate90CounterClockwise:
matrix.translate(0, dst_rect.height());
matrix.rotate_radians(-M_PI / 2);
break;
default:
VERIFY_NOT_REACHED();
}
return matrix;
}
template<typename T>
[[nodiscard]]
static Size<T> exif_oriented_size(Size<T> const& size, Gfx::ExifOrientation orientation)
{
switch (orientation) {
case Gfx::ExifOrientation::Default:
case Gfx::ExifOrientation::FlipHorizontally:
case Gfx::ExifOrientation::Rotate180:
case Gfx::ExifOrientation::FlipVertically:
return size;
case Gfx::ExifOrientation::Rotate90ClockwiseThenFlipHorizontally:
case Gfx::ExifOrientation::Rotate90Clockwise:
case Gfx::ExifOrientation::FlipHorizontallyThenRotate90Clockwise:
case Gfx::ExifOrientation::Rotate90CounterClockwise:
return { size.height(), size.width() };
}
VERIFY_NOT_REACHED();
}
}

View file

@ -23,12 +23,22 @@ struct ImmutableBitmapImpl {
int ImmutableBitmap::width() const
{
return m_impl->sk_image->width();
auto const* bitmap = m_impl->source.get_pointer<NonnullRefPtr<Gfx::Bitmap>>();
if (!bitmap)
return m_impl->sk_image->width();
return (*bitmap)->width();
}
int ImmutableBitmap::height() const
{
return m_impl->sk_image->height();
auto const* bitmap = m_impl->source.get_pointer<NonnullRefPtr<Gfx::Bitmap>>();
if (!bitmap)
return m_impl->sk_image->height();
return (*bitmap)->height();
}
IntRect ImmutableBitmap::rect() const
@ -57,6 +67,14 @@ RefPtr<Gfx::Bitmap const> ImmutableBitmap::bitmap() const
return m_impl->source.get<NonnullRefPtr<Gfx::Bitmap>>();
}
ExifOrientation ImmutableBitmap::get_exif_orientation() const
{
if (auto const* bitmap = m_impl->source.get_pointer<NonnullRefPtr<Gfx::Bitmap>>())
return (*bitmap)->exif_orientation();
return ExifOrientation::Default;
}
Color ImmutableBitmap::get_pixel(int x, int y) const
{
// FIXME: Implement for PaintingSurface

View file

@ -41,6 +41,9 @@ public:
RefPtr<Bitmap const> bitmap() const;
[[nodiscard]]
ExifOrientation get_exif_orientation() const;
private:
NonnullOwnPtr<ImmutableBitmapImpl> m_impl;

View file

@ -142,6 +142,21 @@ void PainterSkia::fill_rect(Gfx::FloatRect const& rect, Color color)
});
}
static SkMatrix to_skia_matrix(Gfx::AffineTransform const& affine_transform)
{
SkScalar affine[6];
affine[0] = affine_transform.a();
affine[1] = affine_transform.b();
affine[2] = affine_transform.c();
affine[3] = affine_transform.d();
affine[4] = affine_transform.e();
affine[5] = affine_transform.f();
SkMatrix matrix;
matrix.setAffine(affine);
return matrix;
}
void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& src_bitmap, Gfx::IntRect const& src_rect, Gfx::ScalingMode scaling_mode, ReadonlySpan<Gfx::Filter> filters, float global_alpha, Gfx::CompositingAndBlendingOperator compositing_and_blending_operator)
{
SkPaint paint;
@ -150,13 +165,24 @@ void PainterSkia::draw_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitm
paint.setBlender(to_skia_blender(compositing_and_blending_operator));
impl().with_canvas([&](auto& canvas) {
auto oriented_dst_rect = dst_rect;
auto const transformation_matrix = compute_exif_orientation_matrix(src_bitmap.get_exif_orientation(), oriented_dst_rect);
auto const skia_transformation_matrix = to_skia_matrix(transformation_matrix);
canvas.save();
canvas.translate(oriented_dst_rect.x(), oriented_dst_rect.y());
canvas.concat(skia_transformation_matrix);
canvas.translate(-oriented_dst_rect.x(), -oriented_dst_rect.y());
canvas.drawImageRect(
src_bitmap.sk_image(),
to_skia_rect(src_rect),
to_skia_rect(dst_rect),
to_skia_rect(oriented_dst_rect),
to_skia_sampling_options(scaling_mode),
&paint,
SkCanvas::kStrict_SrcRectConstraint);
canvas.restore();
});
}

View file

@ -161,6 +161,13 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
// When the source rectangle is outside the source image, the source rectangle must be clipped
// to the source image and the destination rectangle must be clipped in the same proportion.
auto clipped_source = source_rect.intersected(bitmap->rect().to_type<float>());
if (bitmap->bitmap()) {
auto const exif_orientation = bitmap->get_exif_orientation();
auto const oriented_size = Gfx::exif_oriented_size(destination_rect.size(), exif_orientation);
destination_rect.set_size(oriented_size);
}
auto clipped_destination = destination_rect;
if (clipped_source != source_rect) {
clipped_destination.set_width(clipped_destination.width() * (clipped_source.width() / source_rect.width()));

View file

@ -10,6 +10,7 @@
#include <AK/NonnullRefPtr.h>
#include <AK/Utf8View.h>
#include <AK/Vector.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Color.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
#include <LibGfx/Forward.h>
@ -74,6 +75,8 @@ struct DrawScaledImmutableBitmap {
Gfx::IntRect clip_rect;
NonnullRefPtr<Gfx::ImmutableBitmap const> bitmap;
Gfx::ScalingMode scaling_mode;
Gfx::AffineTransform transformation_matrix;
bool rect_correction_necessary;
[[nodiscard]] Gfx::IntRect bounding_rect() const { return clip_rect; }
void translate_by(Gfx::IntPoint const& offset)
@ -91,9 +94,11 @@ struct DrawRepeatedImmutableBitmap {
Gfx::IntRect dst_rect;
Gfx::IntRect clip_rect;
Gfx::IntSize src_size;
NonnullRefPtr<Gfx::ImmutableBitmap const> bitmap;
Gfx::ScalingMode scaling_mode;
Repeat repeat;
Gfx::AffineTransform transformation_matrix;
void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); }
};

View file

@ -53,21 +53,6 @@ static SkRRect to_skia_rrect(auto const& rect, CornerRadii const& corner_radii)
return rrect;
}
static SkMatrix to_skia_matrix(Gfx::AffineTransform const& affine_transform)
{
SkScalar affine[6];
affine[0] = affine_transform.a();
affine[1] = affine_transform.b();
affine[2] = affine_transform.c();
affine[3] = affine_transform.d();
affine[4] = affine_transform.e();
affine[5] = affine_transform.f();
SkMatrix matrix;
matrix.setAffine(affine);
return matrix;
}
void DisplayListPlayerSkia::flush()
{
if (m_context)
@ -133,15 +118,49 @@ void DisplayListPlayerSkia::draw_painting_surface(DrawPaintingSurface const& com
canvas.drawImageRect(image, src_rect, dst_rect, to_skia_sampling_options(command.scaling_mode), &paint, SkCanvas::kStrict_SrcRectConstraint);
}
static SkMatrix to_skia_matrix(Gfx::AffineTransform const& affine_transform)
{
SkScalar affine[6];
affine[0] = affine_transform.a();
affine[1] = affine_transform.b();
affine[2] = affine_transform.c();
affine[3] = affine_transform.d();
affine[4] = affine_transform.e();
affine[5] = affine_transform.f();
SkMatrix matrix;
matrix.setAffine(affine);
return matrix;
}
void DisplayListPlayerSkia::draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const& command)
{
auto dst_rect = to_skia_rect(command.dst_rect);
// Our dst_rect is already correctly rotated, but since we rotate our canvas, our dst_rect now applies to a rotated canvas.
// This means our once correct rect now needs to be rotated 90deg *again* for it to be correct.
// This is equivalent to swapping the width and height of the rect.
// Of course, we don't *always* rotate, so only perform this correction if we are rotating.
auto cmd_dst_rect = command.dst_rect;
if (command.rect_correction_necessary) {
cmd_dst_rect.set_size(cmd_dst_rect.height(), cmd_dst_rect.width());
}
auto dst_rect = to_skia_rect(cmd_dst_rect);
auto clip_rect = to_skia_rect(command.clip_rect);
auto& canvas = surface().canvas();
SkPaint paint;
auto skia_transformation_matrix { to_skia_matrix(command.transformation_matrix) };
canvas.save();
// We clip first so we don't transform the clip rect.
canvas.clipRect(clip_rect);
canvas.translate(dst_rect.x(), dst_rect.y());
canvas.concat(skia_transformation_matrix);
canvas.translate(-dst_rect.x(), -dst_rect.y());
SkPaint const paint;
canvas.drawImageRect(command.bitmap->sk_image(), dst_rect, to_skia_sampling_options(command.scaling_mode), &paint);
canvas.restore();
}
@ -149,9 +168,13 @@ void DisplayListPlayerSkia::draw_repeated_immutable_bitmap(DrawRepeatedImmutable
{
SkMatrix matrix;
auto dst_rect = command.dst_rect.to_type<float>();
auto src_size = command.bitmap->size().to_type<float>();
auto src_size = command.src_size.to_type<float>();
matrix.setScale(dst_rect.width() / src_size.width(), dst_rect.height() / src_size.height());
auto const skia_transformation_matrix { to_skia_matrix(command.transformation_matrix) };
matrix.postConcat(skia_transformation_matrix);
matrix.postTranslate(dst_rect.x(), dst_rect.y());
auto sampling_options = to_skia_sampling_options(command.scaling_mode);
auto tile_mode_x = command.repeat.x ? SkTileMode::kRepeat : SkTileMode::kDecal;

View file

@ -194,26 +194,57 @@ void DisplayListRecorder::draw_painting_surface(Gfx::IntRect const& dst_rect, No
});
}
[[nodiscard]]
static bool is_rect_correction_necessary(Gfx::ExifOrientation exif_orientation)
{
switch (exif_orientation) {
case Gfx::ExifOrientation::Rotate90Clockwise:
case Gfx::ExifOrientation::Rotate90CounterClockwise:
case Gfx::ExifOrientation::FlipHorizontallyThenRotate90Clockwise:
case Gfx::ExifOrientation::Rotate90ClockwiseThenFlipHorizontally:
return true;
default:
return false;
}
}
void DisplayListRecorder::draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::IntRect const& clip_rect, Gfx::ImmutableBitmap const& bitmap, Gfx::ScalingMode scaling_mode)
{
if (dst_rect.is_empty())
return;
auto const exif_orientation = bitmap.get_exif_orientation();
auto const rect_correction_necessary = is_rect_correction_necessary(exif_orientation);
auto const transformation_matrix = compute_exif_orientation_matrix(exif_orientation, dst_rect);
append(DrawScaledImmutableBitmap {
.dst_rect = dst_rect,
.clip_rect = clip_rect,
.bitmap = bitmap,
.scaling_mode = scaling_mode,
.transformation_matrix = transformation_matrix,
.rect_correction_necessary = rect_correction_necessary,
});
}
void DisplayListRecorder::draw_repeated_immutable_bitmap(Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, NonnullRefPtr<Gfx::ImmutableBitmap const> bitmap, Gfx::ScalingMode scaling_mode, DrawRepeatedImmutableBitmap::Repeat repeat)
{
if (dst_rect.is_empty())
return;
auto const exif_orientation = bitmap->get_exif_orientation();
auto const transformation_matrix = compute_exif_orientation_matrix(exif_orientation, dst_rect);
auto const size = bitmap->size();
append(DrawRepeatedImmutableBitmap {
.dst_rect = dst_rect,
.clip_rect = clip_rect,
.src_size = size,
.bitmap = move(bitmap),
.scaling_mode = scaling_mode,
.repeat = repeat,
.transformation_matrix = transformation_matrix,
});
}

View file

@ -352,21 +352,6 @@ TEST_CASE(test_png)
TRY_OR_FAIL(expect_single_frame(*plugin_decoder));
}
TEST_CASE(test_exif)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("png/exif.png"sv)));
EXPECT(Gfx::PNGImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::PNGImageDecoderPlugin::create(file->bytes()));
auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 200, 100 }));
EXPECT(plugin_decoder->metadata().has_value());
auto const& exif_metadata = static_cast<Gfx::ExifMetadata const&>(plugin_decoder->metadata().value());
EXPECT_EQ(*exif_metadata.orientation(), Gfx::TIFF::Orientation::Rotate90Clockwise);
EXPECT_EQ(frame.image->get_pixel(65, 70), Gfx::Color(0, 255, 0));
EXPECT_EQ(frame.image->get_pixel(190, 10), Gfx::Color(255, 0, 0));
}
TEST_CASE(test_png_malformed_frame)
{
Array test_inputs = {

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<style>
* {
margin: 0;
}
body {
background-color: white;
}
</style>
<img src="../images/exif-orientation-png.png">

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Images Module Level 3: image-orientation: from-image</title>
<link rel="author" title="Stephen Chenney" href="mailto:schenney@chromium.org">
<link rel="help" href="https://drafts.csswg.org/css-images-3/#propdef-image-orientation">
<meta name="fuzzy" content="maxDifference=0-100; totalPixels=0-236">
<link rel="match" href="../expected/exif-orientation-png-ref.html" />
<style>
body {
overflow: hidden;
image-orientation: from-image;
}
div {
display: inline-block;
width: 100px;
vertical-align: top;
}
</style>
</head>
<body>
<p>The images should rotate respecting their EXIF orientation because
image-orientation: from-image is specified.</p>
<div>
<img src="../data/exif-orientation-png/exif-orientation-1-ul.png"/>
<br>Normal
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-2-ur.png"/>
<br>Flipped horizontally
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-3-lr.png"/>
<br>Rotated 180&deg;
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-4-lol.png"/>
<br>Flipped vertically
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-5-lu.png"/>
<br>Rotated 90&deg; CCW and flipped vertically
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-6-ru.png"/>
<br>Rotated 90&deg; CW
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-7-rl.png"/>
<br>Rotated 90&deg; CW and flipped vertically
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-8-llo.png"/>
<br>Rotated 90&deg; CCW
</div>
<div>
<img src="../data/exif-orientation-png/exif-orientation-9-u.png"/>
<br>Undefined (invalid value)
</div>
</body>
</html>