From ede0dbafc691f0a1608b9ead749562ae7d62e3c9 Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Thu, 5 Dec 2024 03:19:36 +0400 Subject: [PATCH] AK: Remove Statistics.h This also removes MedianCut and GIFWriter --- AK/Statistics.h | 129 ---------- Libraries/LibGfx/CMakeLists.txt | 2 - Libraries/LibGfx/ImageFormats/GIFWriter.cpp | 264 -------------------- Libraries/LibGfx/ImageFormats/GIFWriter.h | 23 -- Libraries/LibGfx/MedianCut.cpp | 185 -------------- Libraries/LibGfx/MedianCut.h | 53 ---- Tests/AK/CMakeLists.txt | 1 - Tests/AK/TestStatistics.cpp | 70 ------ Tests/LibGfx/CMakeLists.txt | 1 - Tests/LibGfx/TestImageWriter.cpp | 53 ---- Tests/LibGfx/TestMedianCut.cpp | 56 ----- Utilities/animation.cpp | 3 - Utilities/image.cpp | 5 - 13 files changed, 845 deletions(-) delete mode 100644 AK/Statistics.h delete mode 100644 Libraries/LibGfx/ImageFormats/GIFWriter.cpp delete mode 100644 Libraries/LibGfx/ImageFormats/GIFWriter.h delete mode 100644 Libraries/LibGfx/MedianCut.cpp delete mode 100644 Libraries/LibGfx/MedianCut.h delete mode 100644 Tests/AK/TestStatistics.cpp delete mode 100644 Tests/LibGfx/TestMedianCut.cpp diff --git a/AK/Statistics.h b/AK/Statistics.h deleted file mode 100644 index 25d4b64ce7d..00000000000 --- a/AK/Statistics.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2021, Tobias Christiansen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace AK { - -static constexpr int ODD_NAIVE_MEDIAN_CUTOFF = 200; -static constexpr int EVEN_NAIVE_MEDIAN_CUTOFF = 350; - -template> -class Statistics { -public: - Statistics() = default; - ~Statistics() = default; - - explicit Statistics(ContainerType&& existing_container) - : m_values(forward(existing_container)) - { - for (auto const& value : m_values) - m_sum += value; - } - - void add(T const& value) - { - // FIXME: Check for an overflow - m_sum += value; - m_values.append(value); - } - - T const sum() const { return m_sum; } - - // FIXME: Unclear Wording, average can mean a lot of different things - // Median, Arithmetic Mean (which this is), Geometric Mean, Harmonic Mean etc - float average() const - { - // Let's assume the average of an empty dataset is 0 - if (size() == 0) - return 0; - - // TODO: sum might overflow so maybe do multiple partial sums and intermediate divisions here - return (float)sum() / size(); - } - - T const min() const - { - // Lets Rather fail than read over the end of a collection - VERIFY(size() != 0); - - T minimum = m_values[0]; - for (T number : values()) { - if (number < minimum) { - minimum = number; - } - } - return minimum; - } - - T const max() const - { - // Lets Rather fail than read over the end of a collection - VERIFY(size() != 0); - - T maximum = m_values[0]; - for (T number : values()) { - if (number > maximum) { - maximum = number; - } - } - return maximum; - } - - T const median() - { - // Let's assume the Median of an empty dataset is 0 - if (size() == 0) - return 0; - - // If the number of values is even, the median is the arithmetic mean of the two middle values - if (size() <= EVEN_NAIVE_MEDIAN_CUTOFF && size() % 2 == 0) { - quick_sort(m_values); - return (m_values.at(size() / 2) + m_values.at(size() / 2 - 1)) / 2; - } else if (size() <= ODD_NAIVE_MEDIAN_CUTOFF && size() % 2 == 1) { - quick_sort(m_values); - return m_values.at(m_values.size() / 2); - } else if (size() % 2 == 0) { - auto index = size() / 2; - auto median1 = m_values.at(AK::quickselect_inplace(m_values, index)); - auto median2 = m_values.at(AK::quickselect_inplace(m_values, index - 1)); - return (median1 + median2) / 2; - } - return m_values.at(AK::quickselect_inplace(m_values, size() / 2)); - } - - float standard_deviation() const { return sqrt(variance()); } - float variance() const - { - float summation = 0; - float avg = average(); - for (T number : values()) { - float difference = (float)number - avg; - summation += (difference * difference); - } - summation = summation / size(); - return summation; - } - - ContainerType const& values() const { return m_values; } - size_t size() const { return m_values.size(); } - -private: - ContainerType m_values; - T m_sum {}; -}; - -} - -#if USING_AK_GLOBALLY -using AK::Statistics; -#endif diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index 780ecde4595..e0460f56490 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -32,7 +32,6 @@ set(SOURCES ImageFormats/BooleanDecoder.cpp ImageFormats/CCITTDecoder.cpp ImageFormats/GIFLoader.cpp - ImageFormats/GIFWriter.cpp ImageFormats/ICOLoader.cpp ImageFormats/ImageDecoder.cpp ImageFormats/JPEGLoader.cpp @@ -48,7 +47,6 @@ set(SOURCES ImageFormats/WebPWriterLossless.cpp ImageFormats/AVIFLoader.cpp ImmutableBitmap.cpp - MedianCut.cpp PaintingSurface.cpp Palette.cpp Path.cpp diff --git a/Libraries/LibGfx/ImageFormats/GIFWriter.cpp b/Libraries/LibGfx/ImageFormats/GIFWriter.cpp deleted file mode 100644 index 632791c3b76..00000000000 --- a/Libraries/LibGfx/ImageFormats/GIFWriter.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (c) 2024, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -namespace Gfx { - -namespace { - -ErrorOr write_header(Stream& stream) -{ - // 17. Header - TRY(stream.write_until_depleted("GIF89a"sv)); - return {}; -} - -ErrorOr write_logical_descriptor(BigEndianOutputBitStream& stream, IntSize size) -{ - // 18. Logical Screen Descriptor - - if (size.width() > NumericLimits::max() || size.height() > NumericLimits::max()) - return Error::from_string_literal("Bitmap size is too big for a GIF"); - - TRY(stream.write_value(size.width())); - TRY(stream.write_value(size.height())); - - // Global Color Table Flag - TRY(stream.write_bits(false, 1)); - // Color Resolution - TRY(stream.write_bits(6u, 3)); - // Sort Flag - TRY(stream.write_bits(false, 1)); - // Size of Global Color Table - TRY(stream.write_bits(0u, 3)); - - // Background Color Index - TRY(stream.write_value(0)); - - // Pixel Aspect Ratio - // NOTE: We can write a zero as most decoders discard the value. - TRY(stream.write_value(0)); - - return {}; -} - -ErrorOr write_color_table(Stream& stream, ColorPalette const& palette) -{ - // 19. Global Color Table or 21. Local Color Table. - - for (u16 i = 0; i < 256; ++i) { - auto const color = i < palette.palette().size() ? palette.palette()[i] : Color::NamedColor::White; - TRY(stream.write_value(color.red())); - TRY(stream.write_value(color.green())); - TRY(stream.write_value(color.blue())); - } - return {}; -} - -ErrorOr write_image_data(Stream& stream, Bitmap const& bitmap, ColorPalette const& palette) -{ - // 22. Table Based Image Data - auto const pixel_number = static_cast(bitmap.width() * bitmap.height()); - auto indexes = TRY(ByteBuffer::create_uninitialized(pixel_number)); - for (u32 i = 0; i < pixel_number; ++i) { - auto const color = Color::from_argb(*(bitmap.begin() + i)); - indexes[i] = palette.index_of_closest_color(color); - } - - constexpr u8 lzw_minimum_code_size = 8; - auto const encoded = TRY(Compress::LzwCompressor::compress_all(move(indexes), lzw_minimum_code_size)); - - auto const number_of_subblocks = ceil_div(encoded.size(), 255ul); - - TRY(stream.write_value(lzw_minimum_code_size)); - - for (u32 i = 0; i < number_of_subblocks; ++i) { - auto const offset = i * 255; - auto const to_write = min(255, encoded.size() - offset); - TRY(stream.write_value(to_write)); - TRY(stream.write_until_depleted(encoded.bytes().slice(offset, to_write))); - } - - // Block terminator - TRY(stream.write_value(0)); - - return {}; -} - -ErrorOr write_image_descriptor(BigEndianOutputBitStream& stream, Bitmap const& bitmap, IntPoint at = {}) -{ - // 20. Image Descriptor - - // Image Separator - TRY(stream.write_value(0x2c)); - // Image Left Position - TRY(stream.write_value(at.x())); - // Image Top Position - TRY(stream.write_value(at.y())); - // Image Width - TRY(stream.write_value(bitmap.width())); - // Image Height - TRY(stream.write_value(bitmap.height())); - - // Local Color Table Flag - TRY(stream.write_bits(true, 1)); - // Interlace Flag - TRY(stream.write_bits(false, 1)); - // Sort Flag - TRY(stream.write_bits(false, 1)); - // Reserved - TRY(stream.write_bits(0u, 2)); - // Size of Local Color Table - TRY(stream.write_bits(7u, 3)); - return {}; -} - -ErrorOr write_graphic_control_extension(BigEndianOutputBitStream& stream, int duration_ms) -{ - // 23. Graphic Control Extension - - // Extension Introducer - TRY(stream.write_value(0x21)); - // Graphic Control Label - TRY(stream.write_value(0xF9)); - - // Block Size - TRY(stream.write_value(4)); - - // Packed Field - // Reserved - TRY(stream.write_bits(0u, 3)); - // Disposal Method - TRY(stream.write_bits(0u, 3)); - // User Input Flag - TRY(stream.write_bits(false, 1)); - // Transparency Flag - TRY(stream.write_bits(false, 1)); - - // Delay Time - TRY(stream.write_value(duration_ms / 10)); - - // Transparent Color Index - TRY(stream.write_value(0)); - - // Block Terminator - TRY(stream.write_value(0)); - - return {}; -} - -ErrorOr write_trailer(Stream& stream) -{ - TRY(stream.write_value(0x3B)); - return {}; -} - -class GIFAnimationWriter : public AnimationWriter { -public: - GIFAnimationWriter(SeekableStream& stream) - : m_stream(stream) - { - } - - virtual ErrorOr add_frame(Bitmap&, int, IntPoint) override; - -private: - SeekableStream& m_stream; - bool m_is_first_frame { true }; -}; - -ErrorOr GIFAnimationWriter::add_frame(Bitmap& bitmap, int duration_ms, IntPoint at = {}) -{ - // Let's get rid of the previously written trailer - if (!m_is_first_frame) - TRY(m_stream.seek(-1, SeekMode::FromCurrentPosition)); - - m_is_first_frame = false; - - // Write a Table-Based Image - BigEndianOutputBitStream bit_stream { MaybeOwned { m_stream } }; - TRY(write_graphic_control_extension(bit_stream, duration_ms)); - TRY(write_image_descriptor(bit_stream, bitmap, at)); - - auto const palette = TRY(median_cut(bitmap, 256)); - TRY(write_color_table(m_stream, palette)); - TRY(write_image_data(m_stream, bitmap, palette)); - - // We always write a trailer to ensure that the file is valid. - TRY(write_trailer(m_stream)); - - return {}; -} - -ErrorOr write_netscape_extension(BigEndianOutputBitStream& stream, u16 loop_count) -{ - // This is a vendor extension, its sole usage is to provide the loop count. - // I used this link as a source: https://web.archive.org/web/19990418091037/http://www6.uniovi.es/gifanim/gifabout.htm - - // Extension Introducer - TRY(stream.write_value(0x21)); - // Application Extension Label - TRY(stream.write_value(0xFF)); - - // Block Size - constexpr auto netscape_signature = "NETSCAPE2.0"sv; - TRY(stream.write_value(netscape_signature.length())); - TRY(stream.write_until_depleted(netscape_signature)); - - // Length of Data Sub-Block - TRY(stream.write_value(3)); - - // Undocumented - TRY(stream.write_value(1)); - - // Number of loops, 0 means infinite - TRY(stream.write_value(loop_count)); - - // Block Terminator - TRY(stream.write_value(0)); - - return {}; -} - -} - -ErrorOr GIFWriter::encode(Stream& stream, Bitmap const& bitmap) -{ - auto const palette = TRY(median_cut(bitmap, 256)); - TRY(write_header(stream)); - - BigEndianOutputBitStream bit_stream { MaybeOwned { stream } }; - TRY(write_logical_descriptor(bit_stream, bitmap.size())); - - // Write a Table-Based Image - TRY(write_image_descriptor(bit_stream, bitmap)); - TRY(write_color_table(bit_stream, palette)); - TRY(write_image_data(stream, bitmap, palette)); - - TRY(write_trailer(bit_stream)); - - return {}; -} - -ErrorOr> GIFWriter::start_encoding_animation(SeekableStream& stream, IntSize dimensions, u16 loop_count) -{ - TRY(write_header(stream)); - - BigEndianOutputBitStream bit_stream { MaybeOwned { stream } }; - TRY(write_logical_descriptor(bit_stream, dimensions)); - - // Vendor extension to support looping - TRY(write_netscape_extension(bit_stream, loop_count)); - - return make(stream); -} - -} diff --git a/Libraries/LibGfx/ImageFormats/GIFWriter.h b/Libraries/LibGfx/ImageFormats/GIFWriter.h deleted file mode 100644 index 0139c565b62..00000000000 --- a/Libraries/LibGfx/ImageFormats/GIFWriter.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2024, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Gfx { - -// Specified at: https://www.w3.org/Graphics/GIF/spec-gif89a.txt - -class GIFWriter { -public: - static ErrorOr encode(Stream&, Bitmap const&); - static ErrorOr> start_encoding_animation(SeekableStream&, IntSize dimensions, u16 loop_count); -}; - -} diff --git a/Libraries/LibGfx/MedianCut.cpp b/Libraries/LibGfx/MedianCut.cpp deleted file mode 100644 index 1f7b362e1c2..00000000000 --- a/Libraries/LibGfx/MedianCut.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2024, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -namespace Gfx { - -namespace { - -using Bucket = Vector; -using Buckets = Vector; - -void sort_along_color(Bucket& bucket, u8 color_index) -{ - auto less_than = [=](ARGB32 first, ARGB32 second) { - auto const first_color = Color::from_argb(first); - auto const second_color = Color::from_argb(second); - switch (color_index) { - case 0: - return first_color.red() < second_color.red(); - case 1: - return first_color.green() < second_color.green(); - case 2: - return first_color.blue() < second_color.blue(); - default: - VERIFY_NOT_REACHED(); - } - }; - - AK::quick_sort(bucket, less_than); -} - -template -struct MaxAndIndex { - T maximum; - u32 index; -}; - -template -MaxAndIndex max_and_index(Span values, GreaterThan greater_than) -{ - VERIFY(values.size() != 0); - - u32 max_index = 0; - RemoveCV max_value = values[0]; - for (u32 i = 0; i < values.size(); ++i) { - if (greater_than(values[i], max_value)) { - max_value = values[i]; - max_index = i; - } - } - - return { max_value, max_index }; -} - -ErrorOr split_bucket(Buckets& buckets, u32 index_to_split_at, u8 color_index) -{ - auto& to_split = buckets[index_to_split_at]; - - sort_along_color(to_split, color_index); - - Bucket new_bucket {}; - - auto const middle = to_split.size() / 2; - - auto const span_to_move = to_split.span().slice(middle); - // FIXME: Make Vector::try_extend() take a span - for (u32 i = 0; i < span_to_move.size(); ++i) - TRY(new_bucket.try_append(span_to_move[i])); - to_split.remove(middle, span_to_move.size()); - - TRY(buckets.try_append(move(new_bucket))); - - return {}; -} - -struct IndexAndChannel { - u32 bucket_index {}; - float score {}; - u8 color_index {}; -}; - -ErrorOr> find_largest_bucket(Buckets const& buckets) -{ - Vector bucket_stats {}; - - for (u32 i = 0; i < buckets.size(); ++i) { - auto const& bucket = buckets[i]; - - if (bucket.size() == 1) - continue; - - Statistics red {}; - Statistics green {}; - Statistics blue {}; - for (auto const argb : bucket) { - auto const color = Color::from_argb(argb); - red.add(color.red()); - green.add(color.green()); - blue.add(color.blue()); - } - - Array const variances = { red.variance(), green.variance(), blue.variance() }; - - auto const stats = max_and_index(variances.span(), [](auto a, auto b) { return a > b; }); - - TRY(bucket_stats.try_append({ i, stats.maximum, static_cast(stats.index) })); - } - - if (bucket_stats.size() == 0) - return OptionalNone {}; - - return bucket_stats[max_and_index(bucket_stats.span(), [](auto a, auto b) { return a.score > b.score; }).index]; -} - -ErrorOr split_largest_bucket(Buckets& buckets) -{ - if (auto const bucket_info = TRY(find_largest_bucket(buckets)); bucket_info.has_value()) - TRY(split_bucket(buckets, bucket_info->bucket_index, bucket_info->color_index)); - - return {}; -} - -ErrorOr color_palette_from_buckets(Buckets const& buckets) -{ - Vector palette; - HashMap conversion_table; - - for (auto const& bucket : buckets) { - u32 average_r {}; - u32 average_g {}; - u32 average_b {}; - - for (auto const argb : bucket) { - auto const color = Color::from_argb(argb); - average_r += color.red(); - average_g += color.green(); - average_b += color.blue(); - } - - auto const bucket_size = bucket.size(); - auto const average_color = Color( - round_to(static_cast(average_r) / bucket_size), - round_to(static_cast(average_g) / bucket_size), - round_to(static_cast(average_b) / bucket_size)); - - TRY(palette.try_append(average_color)); - for (auto const color : bucket) - TRY(conversion_table.try_set(Color::from_argb(color), { average_color, palette.size() - 1 })); - } - - return ColorPalette { move(palette), move(conversion_table) }; -} - -} - -ErrorOr median_cut(Bitmap const& bitmap, u16 palette_size) -{ - HashTable color_set; - for (auto color : bitmap) - TRY(color_set.try_set(color)); - - Vector first_bucket; - TRY(first_bucket.try_ensure_capacity(color_set.size())); - for (auto const color : color_set) - first_bucket.append(color); - - Buckets bucket_list; - TRY(bucket_list.try_append(first_bucket)); - - u16 old_bucket_size = 0; - while (bucket_list.size() > old_bucket_size && bucket_list.size() < palette_size) { - old_bucket_size = bucket_list.size(); - TRY(split_largest_bucket(bucket_list)); - } - - return color_palette_from_buckets(bucket_list); -} - -} diff --git a/Libraries/LibGfx/MedianCut.h b/Libraries/LibGfx/MedianCut.h deleted file mode 100644 index 771004848ae..00000000000 --- a/Libraries/LibGfx/MedianCut.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Gfx { - -class ColorPalette { -public: - struct ColorAndIndex { - Color color; - size_t index; - }; - - ColorPalette(Vector palette, HashMap conversion_table) - : m_palette(move(palette)) - , m_conversion_table(move(conversion_table)) - { - } - - Vector const& palette() const - { - return m_palette; - } - - Color closest_color(Color input) const - { - return m_palette[index_of_closest_color(input)]; - } - - u32 index_of_closest_color(Color input) const - { - if (auto const result = m_conversion_table.get(input); result.has_value()) - return result->index; - TODO(); - } - -private: - Vector m_palette; - HashMap m_conversion_table; -}; - -ErrorOr median_cut(Bitmap const& bitmap, u16 palette_size); - -} diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index 0a99b0432d9..1cc52d90529 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -66,7 +66,6 @@ set(AK_TEST_SOURCES TestSourceLocation.cpp TestSpan.cpp TestStack.cpp - TestStatistics.cpp TestStdLibExtras.cpp TestString.cpp TestStringFloatingPointConversions.cpp diff --git a/Tests/AK/TestStatistics.cpp b/Tests/AK/TestStatistics.cpp deleted file mode 100644 index 2caa61db8da..00000000000 --- a/Tests/AK/TestStatistics.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2023, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -TEST_CASE(Statistics) -{ - // Setup Test Data - AK::Statistics odd_number_elements; - AK::Statistics even_number_elements; - AK::Statistics odd_number_elements_large; - AK::Statistics even_number_elements_large; - - odd_number_elements.add(5.0); - odd_number_elements.add(4.0); - odd_number_elements.add(3.0); - odd_number_elements.add(2.0); - odd_number_elements.add(1.0); - - even_number_elements.add(6.0); - even_number_elements.add(5.0); - even_number_elements.add(4.0); - even_number_elements.add(3.0); - even_number_elements.add(2.0); - even_number_elements.add(1.0); - - for (int i = 201; i > 0; i--) { - odd_number_elements_large.add(i); - } - - for (int i = 360; i > 0; i--) { - even_number_elements_large.add(i); - } - - // Sum - EXPECT_APPROXIMATE(odd_number_elements.sum(), 15.0); - EXPECT_APPROXIMATE(even_number_elements.sum(), 21.0); - - // Average - EXPECT_APPROXIMATE(odd_number_elements.average(), 3.0); - EXPECT_APPROXIMATE(even_number_elements.average(), 3.5); - - // Min - EXPECT_APPROXIMATE(odd_number_elements.min(), 1.0); - EXPECT_APPROXIMATE(even_number_elements.min(), 1.0); - - // Max - EXPECT_APPROXIMATE(odd_number_elements.max(), 5.0); - EXPECT_APPROXIMATE(even_number_elements.max(), 6.0); - - // Median - EXPECT_APPROXIMATE(odd_number_elements.median(), 3.0); - EXPECT_APPROXIMATE(even_number_elements.median(), 3.5); - EXPECT_APPROXIMATE(odd_number_elements_large.median(), 101.0); - EXPECT_APPROXIMATE(even_number_elements_large.median(), 180.5); - - // The expected values for standard deviation and variance were calculated by my school issued scientific calculator - - // Standard Deviation - EXPECT_APPROXIMATE(odd_number_elements.standard_deviation(), 1.4142135623731); - EXPECT_APPROXIMATE(even_number_elements.standard_deviation(), 1.7078251276599); - - // Variance - EXPECT_APPROXIMATE(odd_number_elements.variance(), 2.0); - EXPECT_APPROXIMATE(even_number_elements.variance(), 2.9166666666667); -} diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index a3bf3f5e0da..4c15de43c1c 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -5,7 +5,6 @@ set(TEST_SOURCES TestICCProfile.cpp TestImageDecoder.cpp TestImageWriter.cpp - TestMedianCut.cpp TestRect.cpp TestWOFF.cpp TestWOFF2.cpp diff --git a/Tests/LibGfx/TestImageWriter.cpp b/Tests/LibGfx/TestImageWriter.cpp index c284fff6324..6e53bf5e87e 100644 --- a/Tests/LibGfx/TestImageWriter.cpp +++ b/Tests/LibGfx/TestImageWriter.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -108,58 +107,6 @@ TEST_CASE(test_bmp) TRY_OR_FAIL((test_roundtrip(TRY_OR_FAIL(create_test_rgba_bitmap())))); } -TEST_CASE(test_gif) -{ - // Let's limit the size of the image so every color can fit in a color table of 256 elements. - auto bitmap = TRY_OR_FAIL(TRY_OR_FAIL(create_test_rgb_bitmap())->cropped({ 0, 0, 16, 16 })); - - auto encoded_bitmap = TRY_OR_FAIL((encode_bitmap(bitmap))); - auto decoder = TRY_OR_FAIL(Gfx::GIFImageDecoderPlugin::create(encoded_bitmap)); - - EXPECT_EQ(decoder->size(), bitmap->size()); - EXPECT_EQ(decoder->frame_count(), 1u); - EXPECT(!decoder->is_animated()); - - expect_bitmaps_equal(*TRY_OR_FAIL(decoder->frame(0)).image, bitmap); -} - -TEST_CASE(test_gif_animated) -{ - auto bitmap_1 = TRY_OR_FAIL(TRY_OR_FAIL(create_test_rgb_bitmap())->cropped({ 0, 0, 16, 16 })); - auto bitmap_2 = TRY_OR_FAIL(TRY_OR_FAIL(create_test_rgb_bitmap())->cropped({ 16, 16, 16, 16 })); - auto bitmap_3 = TRY_OR_FAIL(bitmap_2->clone()); - - bitmap_3->scanline(3)[3] = Color(Color::NamedColor::Red).value(); - - auto stream_buffer = TRY_OR_FAIL(ByteBuffer::create_uninitialized(3072)); - FixedMemoryStream stream { Bytes { stream_buffer } }; - auto animation_writer = TRY_OR_FAIL(Gfx::GIFWriter::start_encoding_animation(stream, bitmap_1->size(), 0)); - TRY_OR_FAIL(animation_writer->add_frame(*bitmap_1, 100)); - TRY_OR_FAIL(animation_writer->add_frame(*bitmap_2, 200)); - TRY_OR_FAIL(animation_writer->add_frame_relative_to_last_frame(*bitmap_3, 200, *bitmap_2)); - - auto encoded_animation = ReadonlyBytes { stream_buffer.data(), stream.offset() }; - - auto decoder = TRY_OR_FAIL(Gfx::GIFImageDecoderPlugin::create(encoded_animation)); - - EXPECT_EQ(decoder->size(), bitmap_1->size()); - EXPECT_EQ(decoder->frame_count(), 3u); - EXPECT_EQ(decoder->loop_count(), 0u); - EXPECT(decoder->is_animated()); - - auto const frame_1 = TRY_OR_FAIL(decoder->frame(0)); - EXPECT_EQ(frame_1.duration, 100); - expect_bitmaps_equal(*frame_1.image, bitmap_1); - - auto const frame_2 = TRY_OR_FAIL(decoder->frame(1)); - EXPECT_EQ(frame_2.duration, 200); - expect_bitmaps_equal(*frame_2.image, bitmap_2); - - auto const frame_3 = TRY_OR_FAIL(decoder->frame(2)); - EXPECT_EQ(frame_3.duration, 200); - expect_bitmaps_equal(*frame_3.image, bitmap_3); -} - TEST_CASE(test_jpeg) { // JPEG is lossy, so the roundtripped bitmap won't match the original bitmap. But it should still have the same size. diff --git a/Tests/LibGfx/TestMedianCut.cpp b/Tests/LibGfx/TestMedianCut.cpp deleted file mode 100644 index f116ae82438..00000000000 --- a/Tests/LibGfx/TestMedianCut.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024, Lucas Chollet - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -TEST_CASE(single_element) -{ - auto const bitmap = TRY_OR_FAIL(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { 1, 1 })); - bitmap->set_pixel(0, 0, Gfx::Color::NamedColor::White); - - auto const result = TRY_OR_FAIL(Gfx::median_cut(bitmap, 1)); - - EXPECT_EQ(result.palette().size(), 1ul); - EXPECT_EQ(result.closest_color(Gfx::Color::NamedColor::White), Gfx::Color::NamedColor::White); -} - -namespace { -constexpr auto colors = to_array({ { 253, 0, 0 }, { 255, 0, 0 }, { 0, 253, 0 }, { 0, 255, 0 } }); - -ErrorOr> create_test_bitmap() -{ - auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { colors.size(), 1 })); - for (u8 i = 0; i < colors.size(); ++i) - bitmap->set_pixel(i, 0, colors[i]); - return bitmap; -} -} - -TEST_CASE(four_in_four_out) -{ - auto const bitmap = TRY_OR_FAIL(create_test_bitmap()); - - auto const result = TRY_OR_FAIL(Gfx::median_cut(bitmap, 4)); - - EXPECT_EQ(result.palette().size(), 4ul); - for (auto const color : colors) - EXPECT_EQ(result.closest_color(color), color); -} - -TEST_CASE(four_in_two_out) -{ - auto const bitmap = TRY_OR_FAIL(create_test_bitmap()); - - auto const result = TRY_OR_FAIL(Gfx::median_cut(bitmap, 2)); - - EXPECT_EQ(result.palette().size(), 2ul); - EXPECT_EQ(result.closest_color(Gfx::Color(253, 0, 0)), Gfx::Color(254, 0, 0)); - EXPECT_EQ(result.closest_color(Gfx::Color(255, 0, 0)), Gfx::Color(254, 0, 0)); - EXPECT_EQ(result.closest_color(Gfx::Color(0, 253, 0)), Gfx::Color(0, 254, 0)); - EXPECT_EQ(result.closest_color(Gfx::Color(0, 255, 0)), Gfx::Color(0, 254, 0)); -} diff --git a/Utilities/animation.cpp b/Utilities/animation.cpp index 319bc1d7bb0..71394d80609 100644 --- a/Utilities/animation.cpp +++ b/Utilities/animation.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -50,8 +49,6 @@ ErrorOr serenity_main(Main::Arguments arguments) auto animation_writer = TRY([&]() -> ErrorOr> { if (options.out_path.ends_with(".webp"sv)) return Gfx::WebPWriter::start_encoding_animation(*output_stream, decoder->size(), decoder->loop_count()); - if (options.out_path.ends_with(".gif"sv)) - return Gfx::GIFWriter::start_encoding_animation(*output_stream, decoder->size(), decoder->loop_count()); return Error::from_string_literal("Unable to find a encoder for the requested extension."); }()); diff --git a/Utilities/image.cpp b/Utilities/image.cpp index 6389ff868af..a9039665211 100644 --- a/Utilities/image.cpp +++ b/Utilities/image.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -167,10 +166,6 @@ static ErrorOr save_image(LoadedImage& image, StringView out_path, u8 jpeg auto& frame = image.bitmap.get>(); - if (out_path.ends_with(".gif"sv, CaseSensitivity::CaseInsensitive)) { - TRY(Gfx::GIFWriter::encode(*TRY(stream()), *frame)); - return {}; - } if (out_path.ends_with(".jpg"sv, CaseSensitivity::CaseInsensitive) || out_path.ends_with(".jpeg"sv, CaseSensitivity::CaseInsensitive)) { TRY(Gfx::JPEGWriter::encode(*TRY(stream()), *frame, { .icc_data = image.icc_data, .quality = jpeg_quality })); return {};