diff --git a/Libraries/LibGfx/BitmapExport.cpp b/Libraries/LibGfx/BitmapExport.cpp new file mode 100644 index 0000000000..99e520a71a --- /dev/null +++ b/Libraries/LibGfx/BitmapExport.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2024, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Gfx { + +StringView export_format_name(ExportFormat format) +{ + switch (format) { +#define ENUMERATE_EXPORT_FORMAT(format) \ + case Gfx::ExportFormat::format: \ + return #format##sv; + ENUMERATE_EXPORT_FORMATS(ENUMERATE_EXPORT_FORMAT) +#undef ENUMERATE_EXPORT_FORMAT + } + VERIFY_NOT_REACHED(); +} + +static int bytes_per_pixel_for_export_format(ExportFormat format) +{ + switch (format) { + case ExportFormat::Gray8: + case ExportFormat::Alpha8: + return 1; + case ExportFormat::RGB565: + case ExportFormat::RGBA5551: + case ExportFormat::RGBA4444: + return 2; + case ExportFormat::RGB888: + return 3; + case ExportFormat::RGBA8888: + return 4; + default: + VERIFY_NOT_REACHED(); + } +} + +static SkColorType export_format_to_skia_color_type(ExportFormat format) +{ + switch (format) { + case ExportFormat::Gray8: + return SkColorType::kGray_8_SkColorType; + case ExportFormat::Alpha8: + return SkColorType::kAlpha_8_SkColorType; + case ExportFormat::RGB565: + return SkColorType::kRGB_565_SkColorType; + case ExportFormat::RGBA5551: + dbgln("FIXME: Support conversion to RGBA5551."); + return SkColorType::kUnknown_SkColorType; + case ExportFormat::RGBA4444: + return SkColorType::kARGB_4444_SkColorType; + case ExportFormat::RGB888: + // This one needs to be converted manually because Skia has no valid 24-bit color type. + VERIFY_NOT_REACHED(); + case ExportFormat::RGBA8888: + return SkColorType::kRGBA_8888_SkColorType; + default: + VERIFY_NOT_REACHED(); + } +} + +ErrorOr export_bitmap_to_byte_buffer( + Bitmap const& bitmap, + ColorSpace const& color_space, + ExportFormat format, + int flags, + Optional target_width, + Optional target_height) +{ + int width = target_width.value_or(bitmap.width()); + int height = target_height.value_or(bitmap.height()); + + if (format == ExportFormat::RGB888 && (width != bitmap.width() || height != bitmap.height())) { + dbgln("FIXME: Ignoring target width and height because scaling is not implemented for this export format."); + width = bitmap.width(); + height = bitmap.height(); + } + + Checked buffer_pitch = width; + int number_of_bytes = bytes_per_pixel_for_export_format(format); + buffer_pitch *= number_of_bytes; + if (buffer_pitch.has_overflow()) + return Error::from_string_literal("Gfx::export_bitmap_to_byte_buffer size overflow"); + + if (Checked::multiplication_would_overflow(buffer_pitch.value(), height)) + return Error::from_string_literal("Gfx::export_bitmap_to_byte_buffer size overflow"); + + auto buffer = MUST(ByteBuffer::create_zeroed(buffer_pitch.value() * height)); + + if (width > 0 && height > 0) { + if (format == ExportFormat::RGB888) { + // 24 bit RGB is not supported by Skia, so we need to handle this format ourselves. + auto* raw_buffer = buffer.data(); + for (auto y = 0; y < height; y++) { + auto target_y = flags & ExportFlags::FlipY ? height - y - 1 : y; + for (auto x = 0; x < width; x++) { + auto pixel = bitmap.get_pixel(x, y); + auto buffer_offset = (target_y * buffer_pitch.value()) + (x * 3ull); + raw_buffer[buffer_offset + 0] = pixel.red(); + raw_buffer[buffer_offset + 1] = pixel.green(); + raw_buffer[buffer_offset + 2] = pixel.blue(); + } + } + } else { + auto image = sk_image_from_bitmap(bitmap, color_space); + if (!image) + return Error::from_string_literal("Failed to create a Skia image for this Bitmap"); + + auto skia_format = export_format_to_skia_color_type(format); + auto skia_color_space = SkColorSpace::MakeSRGB(); + + auto image_info = SkImageInfo::Make( + width, + height, + skia_format, + flags & ExportFlags::PremultiplyAlpha ? SkAlphaType::kPremul_SkAlphaType : SkAlphaType::kUnpremul_SkAlphaType, + skia_color_space); + auto surface = SkSurfaces::WrapPixels(image_info, buffer.data(), buffer_pitch.value()); + VERIFY(surface); + auto* surface_canvas = surface->getCanvas(); + auto dst_rect = Gfx::to_skia_rect(Gfx::Rect { 0, 0, width, height }); + + if (flags & ExportFlags::FlipY) { + surface_canvas->translate(0, dst_rect.height()); + surface_canvas->scale(1, -1); + } + + surface_canvas->drawImageRect( + image.get(), + dst_rect, + Gfx::to_skia_sampling_options(Gfx::ScalingMode::NearestNeighbor)); + } + } else { + VERIFY(buffer.is_empty()); + } + + return BitmapExportResult { + .buffer = move(buffer), + .width = width, + .height = height, + }; +} + +} diff --git a/Libraries/LibGfx/BitmapExport.h b/Libraries/LibGfx/BitmapExport.h new file mode 100644 index 0000000000..553a5c20cf --- /dev/null +++ b/Libraries/LibGfx/BitmapExport.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Gfx { + +#define ENUMERATE_EXPORT_FORMATS(X) \ + X(Gray8) \ + X(Alpha8) \ + X(RGB565) \ + X(RGBA5551) \ + X(RGBA4444) \ + X(RGB888) \ + X(RGBA8888) + +enum class ExportFormat : u8 { +#define ENUMERATE_EXPORT_FORMAT(format) format, + ENUMERATE_EXPORT_FORMATS(ENUMERATE_EXPORT_FORMAT) +#undef ENUMERATE_EXPORT_FORMAT +}; + +[[nodiscard]] StringView export_format_name(ExportFormat); + +struct ExportFlags { + enum : u8 { + PremultiplyAlpha = 1 << 0, + FlipY = 1 << 1, + }; +}; + +struct BitmapExportResult { + ByteBuffer buffer; + int width { 0 }; + int height { 0 }; +}; + +[[nodiscard]] ErrorOr export_bitmap_to_byte_buffer( + Bitmap const&, + ColorSpace const&, + ExportFormat, + int flags, + Optional target_width, + Optional target_height); + +} diff --git a/Libraries/LibGfx/BitmapExportResult.h b/Libraries/LibGfx/BitmapExportResult.h deleted file mode 100644 index 66a46b0aa8..0000000000 --- a/Libraries/LibGfx/BitmapExportResult.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2025, Ladybird contributors - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Gfx { - -struct BitmapExportResult { - ByteBuffer buffer; - int width { 0 }; - int height { 0 }; -}; - -} diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index 52d4f51e23..df47ee1fe1 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -4,6 +4,7 @@ include(vulkan) set(SOURCES AffineTransform.cpp Bitmap.cpp + BitmapExport.cpp BitmapSequence.cpp CMYKBitmap.cpp Color.cpp diff --git a/Libraries/LibGfx/ImmutableBitmap.cpp b/Libraries/LibGfx/ImmutableBitmap.cpp index abd332303a..225046f430 100644 --- a/Libraries/LibGfx/ImmutableBitmap.cpp +++ b/Libraries/LibGfx/ImmutableBitmap.cpp @@ -8,27 +8,9 @@ #include #include #include -#include - -#include -#include -#include -#include namespace Gfx { -StringView export_format_name(ExportFormat format) -{ - switch (format) { -#define ENUMERATE_EXPORT_FORMAT(format) \ - case Gfx::ExportFormat::format: \ - return #format##sv; - ENUMERATE_EXPORT_FORMATS(ENUMERATE_EXPORT_FORMAT) -#undef ENUMERATE_EXPORT_FORMAT - } - VERIFY_NOT_REACHED(); -} - int ImmutableBitmap::width() const { return m_bitmap->width(); @@ -59,117 +41,6 @@ ColorSpace const& ImmutableBitmap::color_space() const return m_color_space; } -static int bytes_per_pixel_for_export_format(ExportFormat format) -{ - switch (format) { - case ExportFormat::Gray8: - case ExportFormat::Alpha8: - return 1; - case ExportFormat::RGB565: - case ExportFormat::RGBA5551: - case ExportFormat::RGBA4444: - return 2; - case ExportFormat::RGB888: - return 3; - case ExportFormat::RGBA8888: - return 4; - default: - VERIFY_NOT_REACHED(); - } -} - -static SkColorType export_format_to_skia_color_type(ExportFormat format) -{ - switch (format) { - case ExportFormat::Gray8: - return SkColorType::kGray_8_SkColorType; - case ExportFormat::Alpha8: - return SkColorType::kAlpha_8_SkColorType; - case ExportFormat::RGB565: - return SkColorType::kRGB_565_SkColorType; - case ExportFormat::RGBA5551: - dbgln("FIXME: Support conversion to RGBA5551."); - return SkColorType::kUnknown_SkColorType; - case ExportFormat::RGBA4444: - return SkColorType::kARGB_4444_SkColorType; - case ExportFormat::RGB888: - // This one needs to be converted manually because Skia has no valid 24-bit color type. - VERIFY_NOT_REACHED(); - case ExportFormat::RGBA8888: - return SkColorType::kRGBA_8888_SkColorType; - default: - VERIFY_NOT_REACHED(); - } -} - -ErrorOr ImmutableBitmap::export_to_byte_buffer(ExportFormat format, int flags, Optional target_width, Optional target_height) const -{ - int width = target_width.value_or(this->width()); - int height = target_height.value_or(this->height()); - - if (format == ExportFormat::RGB888 && (width != this->width() || height != this->height())) { - dbgln("FIXME: Ignoring target width and height because scaling is not implemented for this export format."); - width = this->width(); - height = this->height(); - } - - Checked buffer_pitch = width; - int number_of_bytes = bytes_per_pixel_for_export_format(format); - buffer_pitch *= number_of_bytes; - if (buffer_pitch.has_overflow()) - return Error::from_string_literal("Gfx::ImmutableBitmap::export_to_byte_buffer size overflow"); - - if (Checked::multiplication_would_overflow(buffer_pitch.value(), height)) - return Error::from_string_literal("Gfx::ImmutableBitmap::export_to_byte_buffer size overflow"); - - auto buffer = MUST(ByteBuffer::create_zeroed(buffer_pitch.value() * height)); - - if (width > 0 && height > 0) { - if (format == ExportFormat::RGB888) { - // 24 bit RGB is not supported by Skia, so we need to handle this format ourselves. - auto* raw_buffer = buffer.data(); - for (auto y = 0; y < height; y++) { - auto target_y = flags & ExportFlags::FlipY ? height - y - 1 : y; - for (auto x = 0; x < width; x++) { - auto pixel = get_pixel(x, y); - auto buffer_offset = (target_y * buffer_pitch.value()) + (x * 3ull); - raw_buffer[buffer_offset + 0] = pixel.red(); - raw_buffer[buffer_offset + 1] = pixel.green(); - raw_buffer[buffer_offset + 2] = pixel.blue(); - } - } - } else { - auto image = sk_image_from_bitmap(*m_bitmap, m_color_space); - if (!image) - return Error::from_string_literal("Failed to create a Skia image for this ImmutableBitmap"); - - auto skia_format = export_format_to_skia_color_type(format); - auto color_space = SkColorSpace::MakeSRGB(); - - auto image_info = SkImageInfo::Make(width, height, skia_format, flags & ExportFlags::PremultiplyAlpha ? SkAlphaType::kPremul_SkAlphaType : SkAlphaType::kUnpremul_SkAlphaType, color_space); - auto surface = SkSurfaces::WrapPixels(image_info, buffer.data(), buffer_pitch.value()); - VERIFY(surface); - auto* surface_canvas = surface->getCanvas(); - auto dst_rect = Gfx::to_skia_rect(Gfx::Rect { 0, 0, width, height }); - - if (flags & ExportFlags::FlipY) { - surface_canvas->translate(0, dst_rect.height()); - surface_canvas->scale(1, -1); - } - - surface_canvas->drawImageRect(image.get(), dst_rect, Gfx::to_skia_sampling_options(Gfx::ScalingMode::NearestNeighbor)); - } - } else { - VERIFY(buffer.is_empty()); - } - - return BitmapExportResult { - .buffer = move(buffer), - .width = width, - .height = height, - }; -} - RefPtr ImmutableBitmap::bitmap() const { return m_bitmap; diff --git a/Libraries/LibGfx/ImmutableBitmap.h b/Libraries/LibGfx/ImmutableBitmap.h index 12e37cd911..12a553f6b1 100644 --- a/Libraries/LibGfx/ImmutableBitmap.h +++ b/Libraries/LibGfx/ImmutableBitmap.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -18,30 +17,6 @@ namespace Gfx { -#define ENUMERATE_EXPORT_FORMATS(X) \ - X(Gray8) \ - X(Alpha8) \ - X(RGB565) \ - X(RGBA5551) \ - X(RGBA4444) \ - X(RGB888) \ - X(RGBA8888) - -enum class ExportFormat : u8 { -#define ENUMERATE_EXPORT_FORMAT(format) format, - ENUMERATE_EXPORT_FORMATS(ENUMERATE_EXPORT_FORMAT) -#undef ENUMERATE_EXPORT_FORMAT -}; - -[[nodiscard]] StringView export_format_name(ExportFormat); - -struct ExportFlags { - enum : u8 { - PremultiplyAlpha = 1 << 0, - FlipY = 1 << 1, - }; -}; - class ImmutableBitmap final : public AtomicRefCounted { public: static NonnullRefPtr create(NonnullRefPtr const& bitmap, ColorSpace color_space = {}); @@ -58,8 +33,6 @@ public: AlphaType alpha_type() const; ColorSpace const& color_space() const; - [[nodiscard]] ErrorOr export_to_byte_buffer(ExportFormat format, int flags, Optional target_width, Optional target_height) const; - Color get_pixel(int x, int y) const; RefPtr bitmap() const; diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.cpp b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.cpp index 827239616e..7d2da52e54 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.cpp +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.cpp @@ -12,6 +12,7 @@ extern "C" { #include } +#include #include #include #include @@ -302,7 +303,17 @@ Optional WebGLRenderingContextBase::read_and_pixel_conv if (m_unpack_premultiply_alpha) export_flags |= Gfx::ExportFlags::PremultiplyAlpha; - auto result = bitmap->export_to_byte_buffer(export_format.value(), export_flags, destination_width, destination_height); + auto source_bitmap = bitmap->bitmap(); + if (!source_bitmap) + return OptionalNone {}; + + auto result = Gfx::export_bitmap_to_byte_buffer( + *source_bitmap, + bitmap->color_space(), + export_format.value(), + export_flags, + destination_width, + destination_height); if (result.is_error()) { dbgln("Could not export bitmap: {}", result.release_error()); return OptionalNone {}; diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h index e79f34c6da..a694ec3588 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include #include #include diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index 0088496c24..23470d79f3 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -2,10 +2,10 @@ include(skia) set(TEST_SOURCES BenchmarkJPEGLoader.cpp + TestBitmapExport.cpp TestColor.cpp TestImageDecoder.cpp TestImageWriter.cpp - TestImmutableBitmap.cpp TestQuad.cpp TestRect.cpp TestWOFF.cpp diff --git a/Tests/LibGfx/TestImmutableBitmap.cpp b/Tests/LibGfx/TestBitmapExport.cpp similarity index 96% rename from Tests/LibGfx/TestImmutableBitmap.cpp rename to Tests/LibGfx/TestBitmapExport.cpp index de9050acf8..cd3f7a2418 100644 --- a/Tests/LibGfx/TestImmutableBitmap.cpp +++ b/Tests/LibGfx/TestBitmapExport.cpp @@ -6,11 +6,11 @@ #include #include +#include #include -#include #include -TEST_CASE(export_to_byte_buffer) +TEST_CASE(export_bitmap_to_byte_buffer) { enum class Premultiplied : u8 { Yes, @@ -229,8 +229,13 @@ TEST_CASE(export_to_byte_buffer) bitmap->set_pixel(0, logical_y1, Color::from_bgra(subtest.source_pixels[2])); bitmap->set_pixel(1, logical_y1, Color::from_bgra(subtest.source_pixels[3])); - auto immutable_bitmap = Gfx::ImmutableBitmap::create(bitmap); - auto result = MUST(immutable_bitmap->export_to_byte_buffer(subtest.export_format, export_flags, 2, 2)); + auto result = MUST(Gfx::export_bitmap_to_byte_buffer( + *bitmap, + {}, + subtest.export_format, + export_flags, + 2, + 2)); EXPECT_EQ(result.width, 2); EXPECT_EQ(result.height, 2);