ladybird/Libraries/LibGfx/ImageFormats/ExifOrientedBitmap.h

119 lines
3.7 KiB
C
Raw Normal View History

/*
* Copyright (c) 2023-2024, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
#include <AK/NonnullOwnPtr.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/TIFFMetadata.h>
namespace Gfx {
namespace Detail {
template<typename BitmapLike>
class ExifOrientedBitmap {
public:
static ErrorOr<ExifOrientedBitmap> create(TIFF::Orientation orientation, IntSize size, BitmapFormat format)
requires(SameAs<BitmapLike, Bitmap>)
{
auto bitmap = TRY(Bitmap::create(format, oriented_size(size, orientation)));
return ExifOrientedBitmap(move(bitmap), size, orientation);
}
static ErrorOr<ExifOrientedBitmap> create(TIFF::Orientation orientation, IntSize size)
requires(SameAs<BitmapLike, CMYKBitmap>)
{
auto bitmap = TRY(CMYKBitmap::create_with_size(oriented_size(size, orientation)));
return ExifOrientedBitmap(move(bitmap), size, orientation);
}
Everywhere: Unify naming of RGBA-like colors The `Bitmap` type was referring to to its internal pixel format by a name that represents the order of the color components as they are layed out in memory. Contrary, the `Color` type was using a naming that where the name represents the order of the components from most to least significant byte when viewed as a unsigned 32bit integer. This is confusing as you have to keep remembering which mental model to use depending on which code you work with. To unify the two, the naming of RGBA-like colors in the `Color` type has been adjusted to match the one from the Bitmap type. This seems to be generally in line with how web APIs think about these types: * `ImageData.pixelFormat` can be `rgba-8unorm` backed by a `Uint8ClamedArray`, but there is no pixel format backed by a 32bit unsigned type. * WebGL can use format `RGBA` with type `UNSIGNED_BYTE`, but there is no such format with type `UNSIGNED_INT`. Additionally, it appears that other browsers and browser-adjacent libraries also think similarly about these types: * Firefox: https://github.com/mozilla-firefox/firefox/blob/main/gfx/2d/Types.h * WebKit: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/PixelFormat.h * Skia: https://chromium.googlesource.com/skia/+/refs/heads/main/include/core/SkColorType.h This has the not so nice side effect that APIs that interact with these types through 32bit unsigned integers now have the component order inverted due to little-endian byte order. E.g. specifying a color as hex constant needs to be done as `0xAABBGGRR` if it is to be treated as RGBA8888. We could alleviate this by providing endian-independent APIs to callers. But I suspect long-term we might want to think differently about bitmap data anyway, e.g. to better support HDR in the future. However, such changes would be more involved than just unifying the naming as done here. So I considered that out of scope for now.
2025-11-23 13:07:38 +01:00
template<OneOf<BGRA8888, CMYK> Value>
void set_pixel(u32 x, u32 y, Value color)
{
auto const new_position = oriented_position(IntPoint(x, y));
m_bitmap->scanline(new_position.y())[new_position.x()] = color;
}
NonnullRefPtr<BitmapLike>& bitmap()
{
return m_bitmap;
}
static IntSize oriented_size(IntSize size, TIFF::Orientation orientation)
{
switch (orientation) {
case Orientation::Default:
case Orientation::FlipHorizontally:
case Orientation::Rotate180:
case Orientation::FlipVertically:
return size;
case Orientation::Rotate90ClockwiseThenFlipHorizontally:
case Orientation::Rotate90Clockwise:
case Orientation::FlipHorizontallyThenRotate90Clockwise:
case Orientation::Rotate90CounterClockwise:
return { size.height(), size.width() };
}
VERIFY_NOT_REACHED();
}
private:
using Orientation = TIFF::Orientation;
ExifOrientedBitmap(NonnullRefPtr<BitmapLike> bitmap, IntSize size, Orientation orientation)
: m_bitmap(move(bitmap))
, m_orientation(orientation)
, m_width(size.width())
, m_height(size.height())
{
}
IntPoint oriented_position(IntPoint point)
{
auto const flip_horizontally = [this](IntPoint point) {
return IntPoint(m_width - point.x() - 1, point.y());
};
auto const rotate_90_clockwise = [this](IntPoint point) {
return IntPoint(m_height - point.y() - 1, point.x());
};
switch (m_orientation) {
case Orientation::Default:
return point;
case Orientation::FlipHorizontally:
return flip_horizontally(point);
case Orientation::Rotate180:
return IntPoint(m_width - point.x() - 1, m_height - point.y() - 1);
case Orientation::FlipVertically:
return IntPoint(point.x(), m_height - point.y() - 1);
case Orientation::Rotate90ClockwiseThenFlipHorizontally:
return flip_horizontally(rotate_90_clockwise(point));
case Orientation::Rotate90Clockwise:
return rotate_90_clockwise(point);
case Orientation::FlipHorizontallyThenRotate90Clockwise:
return rotate_90_clockwise(flip_horizontally(point));
case Orientation::Rotate90CounterClockwise:
return IntPoint(point.y(), m_width - point.x() - 1);
}
VERIFY_NOT_REACHED();
}
NonnullRefPtr<BitmapLike> m_bitmap;
Orientation m_orientation;
u32 m_width {};
u32 m_height {};
};
}
using ExifOrientedBitmap = Detail::ExifOrientedBitmap<Bitmap>;
using ExifOrientedCMYKBitmap = Detail::ExifOrientedBitmap<CMYKBitmap>;
}