mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-09 17:49:40 +00:00
AK: Remove Statistics.h
This also removes MedianCut and GIFWriter
This commit is contained in:
parent
ae6edfb845
commit
ede0dbafc6
Notes:
github-actions[bot]
2024-12-05 15:54:23 +00:00
Author: https://github.com/shlyakpavel
Commit: ede0dbafc6
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2772
13 changed files with 0 additions and 845 deletions
129
AK/Statistics.h
129
AK/Statistics.h
|
@ -1,129 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AK/Concepts.h>
|
|
||||||
#include <AK/Math.h>
|
|
||||||
#include <AK/QuickSelect.h>
|
|
||||||
#include <AK/QuickSort.h>
|
|
||||||
#include <AK/Vector.h>
|
|
||||||
|
|
||||||
namespace AK {
|
|
||||||
|
|
||||||
static constexpr int ODD_NAIVE_MEDIAN_CUTOFF = 200;
|
|
||||||
static constexpr int EVEN_NAIVE_MEDIAN_CUTOFF = 350;
|
|
||||||
|
|
||||||
template<Arithmetic T = float, typename ContainerType = Vector<T>>
|
|
||||||
class Statistics {
|
|
||||||
public:
|
|
||||||
Statistics() = default;
|
|
||||||
~Statistics() = default;
|
|
||||||
|
|
||||||
explicit Statistics(ContainerType&& existing_container)
|
|
||||||
: m_values(forward<ContainerType>(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
|
|
|
@ -32,7 +32,6 @@ set(SOURCES
|
||||||
ImageFormats/BooleanDecoder.cpp
|
ImageFormats/BooleanDecoder.cpp
|
||||||
ImageFormats/CCITTDecoder.cpp
|
ImageFormats/CCITTDecoder.cpp
|
||||||
ImageFormats/GIFLoader.cpp
|
ImageFormats/GIFLoader.cpp
|
||||||
ImageFormats/GIFWriter.cpp
|
|
||||||
ImageFormats/ICOLoader.cpp
|
ImageFormats/ICOLoader.cpp
|
||||||
ImageFormats/ImageDecoder.cpp
|
ImageFormats/ImageDecoder.cpp
|
||||||
ImageFormats/JPEGLoader.cpp
|
ImageFormats/JPEGLoader.cpp
|
||||||
|
@ -48,7 +47,6 @@ set(SOURCES
|
||||||
ImageFormats/WebPWriterLossless.cpp
|
ImageFormats/WebPWriterLossless.cpp
|
||||||
ImageFormats/AVIFLoader.cpp
|
ImageFormats/AVIFLoader.cpp
|
||||||
ImmutableBitmap.cpp
|
ImmutableBitmap.cpp
|
||||||
MedianCut.cpp
|
|
||||||
PaintingSurface.cpp
|
PaintingSurface.cpp
|
||||||
Palette.cpp
|
Palette.cpp
|
||||||
Path.cpp
|
Path.cpp
|
||||||
|
|
|
@ -1,264 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AK/BitStream.h>
|
|
||||||
#include <LibCompress/Lzw.h>
|
|
||||||
#include <LibGfx/Bitmap.h>
|
|
||||||
#include <LibGfx/ImageFormats/GIFWriter.h>
|
|
||||||
#include <LibGfx/MedianCut.h>
|
|
||||||
|
|
||||||
namespace Gfx {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
ErrorOr<void> write_header(Stream& stream)
|
|
||||||
{
|
|
||||||
// 17. Header
|
|
||||||
TRY(stream.write_until_depleted("GIF89a"sv));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<void> write_logical_descriptor(BigEndianOutputBitStream& stream, IntSize size)
|
|
||||||
{
|
|
||||||
// 18. Logical Screen Descriptor
|
|
||||||
|
|
||||||
if (size.width() > NumericLimits<u16>::max() || size.height() > NumericLimits<u16>::max())
|
|
||||||
return Error::from_string_literal("Bitmap size is too big for a GIF");
|
|
||||||
|
|
||||||
TRY(stream.write_value<u16>(size.width()));
|
|
||||||
TRY(stream.write_value<u16>(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<u8>(0));
|
|
||||||
|
|
||||||
// Pixel Aspect Ratio
|
|
||||||
// NOTE: We can write a zero as most decoders discard the value.
|
|
||||||
TRY(stream.write_value<u8>(0));
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<void> 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<u8>(color.red()));
|
|
||||||
TRY(stream.write_value<u8>(color.green()));
|
|
||||||
TRY(stream.write_value<u8>(color.blue()));
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<void> write_image_data(Stream& stream, Bitmap const& bitmap, ColorPalette const& palette)
|
|
||||||
{
|
|
||||||
// 22. Table Based Image Data
|
|
||||||
auto const pixel_number = static_cast<u32>(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<u8>(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<u8>(to_write));
|
|
||||||
TRY(stream.write_until_depleted(encoded.bytes().slice(offset, to_write)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block terminator
|
|
||||||
TRY(stream.write_value<u8>(0));
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<void> write_image_descriptor(BigEndianOutputBitStream& stream, Bitmap const& bitmap, IntPoint at = {})
|
|
||||||
{
|
|
||||||
// 20. Image Descriptor
|
|
||||||
|
|
||||||
// Image Separator
|
|
||||||
TRY(stream.write_value<u8>(0x2c));
|
|
||||||
// Image Left Position
|
|
||||||
TRY(stream.write_value<u16>(at.x()));
|
|
||||||
// Image Top Position
|
|
||||||
TRY(stream.write_value<u16>(at.y()));
|
|
||||||
// Image Width
|
|
||||||
TRY(stream.write_value<u16>(bitmap.width()));
|
|
||||||
// Image Height
|
|
||||||
TRY(stream.write_value<u16>(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<void> write_graphic_control_extension(BigEndianOutputBitStream& stream, int duration_ms)
|
|
||||||
{
|
|
||||||
// 23. Graphic Control Extension
|
|
||||||
|
|
||||||
// Extension Introducer
|
|
||||||
TRY(stream.write_value<u8>(0x21));
|
|
||||||
// Graphic Control Label
|
|
||||||
TRY(stream.write_value<u8>(0xF9));
|
|
||||||
|
|
||||||
// Block Size
|
|
||||||
TRY(stream.write_value<u8>(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<u16>(duration_ms / 10));
|
|
||||||
|
|
||||||
// Transparent Color Index
|
|
||||||
TRY(stream.write_value<u8>(0));
|
|
||||||
|
|
||||||
// Block Terminator
|
|
||||||
TRY(stream.write_value<u8>(0));
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<void> write_trailer(Stream& stream)
|
|
||||||
{
|
|
||||||
TRY(stream.write_value<u8>(0x3B));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
class GIFAnimationWriter : public AnimationWriter {
|
|
||||||
public:
|
|
||||||
GIFAnimationWriter(SeekableStream& stream)
|
|
||||||
: m_stream(stream)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ErrorOr<void> add_frame(Bitmap&, int, IntPoint) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
SeekableStream& m_stream;
|
|
||||||
bool m_is_first_frame { true };
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorOr<void> 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<void> 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<u8>(0x21));
|
|
||||||
// Application Extension Label
|
|
||||||
TRY(stream.write_value<u8>(0xFF));
|
|
||||||
|
|
||||||
// Block Size
|
|
||||||
constexpr auto netscape_signature = "NETSCAPE2.0"sv;
|
|
||||||
TRY(stream.write_value<u8>(netscape_signature.length()));
|
|
||||||
TRY(stream.write_until_depleted(netscape_signature));
|
|
||||||
|
|
||||||
// Length of Data Sub-Block
|
|
||||||
TRY(stream.write_value<u8>(3));
|
|
||||||
|
|
||||||
// Undocumented
|
|
||||||
TRY(stream.write_value<u8>(1));
|
|
||||||
|
|
||||||
// Number of loops, 0 means infinite
|
|
||||||
TRY(stream.write_value<u16>(loop_count));
|
|
||||||
|
|
||||||
// Block Terminator
|
|
||||||
TRY(stream.write_value<u8>(0));
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<void> GIFWriter::encode(Stream& stream, Bitmap const& bitmap)
|
|
||||||
{
|
|
||||||
auto const palette = TRY(median_cut(bitmap, 256));
|
|
||||||
TRY(write_header(stream));
|
|
||||||
|
|
||||||
BigEndianOutputBitStream bit_stream { MaybeOwned<Stream> { 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<NonnullOwnPtr<AnimationWriter>> GIFWriter::start_encoding_animation(SeekableStream& stream, IntSize dimensions, u16 loop_count)
|
|
||||||
{
|
|
||||||
TRY(write_header(stream));
|
|
||||||
|
|
||||||
BigEndianOutputBitStream bit_stream { MaybeOwned<Stream> { stream } };
|
|
||||||
TRY(write_logical_descriptor(bit_stream, dimensions));
|
|
||||||
|
|
||||||
// Vendor extension to support looping
|
|
||||||
TRY(write_netscape_extension(bit_stream, loop_count));
|
|
||||||
|
|
||||||
return make<GIFAnimationWriter>(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AK/Error.h>
|
|
||||||
#include <LibGfx/Forward.h>
|
|
||||||
#include <LibGfx/ImageFormats/AnimationWriter.h>
|
|
||||||
|
|
||||||
namespace Gfx {
|
|
||||||
|
|
||||||
// Specified at: https://www.w3.org/Graphics/GIF/spec-gif89a.txt
|
|
||||||
|
|
||||||
class GIFWriter {
|
|
||||||
public:
|
|
||||||
static ErrorOr<void> encode(Stream&, Bitmap const&);
|
|
||||||
static ErrorOr<NonnullOwnPtr<AnimationWriter>> start_encoding_animation(SeekableStream&, IntSize dimensions, u16 loop_count);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AK/QuickSort.h>
|
|
||||||
#include <AK/Statistics.h>
|
|
||||||
#include <LibGfx/MedianCut.h>
|
|
||||||
|
|
||||||
namespace Gfx {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
using Bucket = Vector<ARGB32>;
|
|
||||||
using Buckets = Vector<Bucket>;
|
|
||||||
|
|
||||||
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<typename T>
|
|
||||||
struct MaxAndIndex {
|
|
||||||
T maximum;
|
|
||||||
u32 index;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T, class GreaterThan>
|
|
||||||
MaxAndIndex<T> max_and_index(Span<T> values, GreaterThan greater_than)
|
|
||||||
{
|
|
||||||
VERIFY(values.size() != 0);
|
|
||||||
|
|
||||||
u32 max_index = 0;
|
|
||||||
RemoveCV<T> 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<void> 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<Optional<IndexAndChannel>> find_largest_bucket(Buckets const& buckets)
|
|
||||||
{
|
|
||||||
Vector<IndexAndChannel> bucket_stats {};
|
|
||||||
|
|
||||||
for (u32 i = 0; i < buckets.size(); ++i) {
|
|
||||||
auto const& bucket = buckets[i];
|
|
||||||
|
|
||||||
if (bucket.size() == 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Statistics<u32> red {};
|
|
||||||
Statistics<u32> green {};
|
|
||||||
Statistics<u32> 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<u8>(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<void> 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<ColorPalette> color_palette_from_buckets(Buckets const& buckets)
|
|
||||||
{
|
|
||||||
Vector<Color> palette;
|
|
||||||
HashMap<Color, ColorPalette::ColorAndIndex> 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<u32>(static_cast<double>(average_r) / bucket_size),
|
|
||||||
round_to<u32>(static_cast<double>(average_g) / bucket_size),
|
|
||||||
round_to<u32>(static_cast<double>(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<ColorPalette> median_cut(Bitmap const& bitmap, u16 palette_size)
|
|
||||||
{
|
|
||||||
HashTable<ARGB32> color_set;
|
|
||||||
for (auto color : bitmap)
|
|
||||||
TRY(color_set.try_set(color));
|
|
||||||
|
|
||||||
Vector<ARGB32> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AK/HashMap.h>
|
|
||||||
#include <AK/Vector.h>
|
|
||||||
#include <LibGfx/Bitmap.h>
|
|
||||||
#include <LibGfx/Color.h>
|
|
||||||
|
|
||||||
namespace Gfx {
|
|
||||||
|
|
||||||
class ColorPalette {
|
|
||||||
public:
|
|
||||||
struct ColorAndIndex {
|
|
||||||
Color color;
|
|
||||||
size_t index;
|
|
||||||
};
|
|
||||||
|
|
||||||
ColorPalette(Vector<Color> palette, HashMap<Color, ColorAndIndex> conversion_table)
|
|
||||||
: m_palette(move(palette))
|
|
||||||
, m_conversion_table(move(conversion_table))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<Color> 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<Color> m_palette;
|
|
||||||
HashMap<Color, ColorAndIndex> m_conversion_table;
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorOr<ColorPalette> median_cut(Bitmap const& bitmap, u16 palette_size);
|
|
||||||
|
|
||||||
}
|
|
|
@ -66,7 +66,6 @@ set(AK_TEST_SOURCES
|
||||||
TestSourceLocation.cpp
|
TestSourceLocation.cpp
|
||||||
TestSpan.cpp
|
TestSpan.cpp
|
||||||
TestStack.cpp
|
TestStack.cpp
|
||||||
TestStatistics.cpp
|
|
||||||
TestStdLibExtras.cpp
|
TestStdLibExtras.cpp
|
||||||
TestString.cpp
|
TestString.cpp
|
||||||
TestStringFloatingPointConversions.cpp
|
TestStringFloatingPointConversions.cpp
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023, the SerenityOS developers.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AK/Statistics.h>
|
|
||||||
#include <LibTest/TestSuite.h>
|
|
||||||
|
|
||||||
TEST_CASE(Statistics)
|
|
||||||
{
|
|
||||||
// Setup Test Data
|
|
||||||
AK::Statistics<double> odd_number_elements;
|
|
||||||
AK::Statistics<double> even_number_elements;
|
|
||||||
AK::Statistics<double> odd_number_elements_large;
|
|
||||||
AK::Statistics<double> 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);
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ set(TEST_SOURCES
|
||||||
TestICCProfile.cpp
|
TestICCProfile.cpp
|
||||||
TestImageDecoder.cpp
|
TestImageDecoder.cpp
|
||||||
TestImageWriter.cpp
|
TestImageWriter.cpp
|
||||||
TestMedianCut.cpp
|
|
||||||
TestRect.cpp
|
TestRect.cpp
|
||||||
TestWOFF.cpp
|
TestWOFF.cpp
|
||||||
TestWOFF2.cpp
|
TestWOFF2.cpp
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#include <LibGfx/ImageFormats/BMPLoader.h>
|
#include <LibGfx/ImageFormats/BMPLoader.h>
|
||||||
#include <LibGfx/ImageFormats/BMPWriter.h>
|
#include <LibGfx/ImageFormats/BMPWriter.h>
|
||||||
#include <LibGfx/ImageFormats/GIFLoader.h>
|
#include <LibGfx/ImageFormats/GIFLoader.h>
|
||||||
#include <LibGfx/ImageFormats/GIFWriter.h>
|
|
||||||
#include <LibGfx/ImageFormats/JPEGLoader.h>
|
#include <LibGfx/ImageFormats/JPEGLoader.h>
|
||||||
#include <LibGfx/ImageFormats/JPEGWriter.h>
|
#include <LibGfx/ImageFormats/JPEGWriter.h>
|
||||||
#include <LibGfx/ImageFormats/PNGLoader.h>
|
#include <LibGfx/ImageFormats/PNGLoader.h>
|
||||||
|
@ -108,58 +107,6 @@ TEST_CASE(test_bmp)
|
||||||
TRY_OR_FAIL((test_roundtrip<Gfx::BMPWriter, Gfx::BMPImageDecoderPlugin>(TRY_OR_FAIL(create_test_rgba_bitmap()))));
|
TRY_OR_FAIL((test_roundtrip<Gfx::BMPWriter, Gfx::BMPImageDecoderPlugin>(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<Gfx::GIFWriter>(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)
|
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.
|
// JPEG is lossy, so the roundtripped bitmap won't match the original bitmap. But it should still have the same size.
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <LibGfx/Bitmap.h>
|
|
||||||
#include <LibGfx/MedianCut.h>
|
|
||||||
#include <LibTest/TestCase.h>
|
|
||||||
|
|
||||||
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<Gfx::Color>({ { 253, 0, 0 }, { 255, 0, 0 }, { 0, 253, 0 }, { 0, 255, 0 } });
|
|
||||||
|
|
||||||
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> 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));
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibCore/MappedFile.h>
|
#include <LibCore/MappedFile.h>
|
||||||
#include <LibGfx/ImageFormats/AnimationWriter.h>
|
#include <LibGfx/ImageFormats/AnimationWriter.h>
|
||||||
#include <LibGfx/ImageFormats/GIFWriter.h>
|
|
||||||
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
||||||
#include <LibGfx/ImageFormats/WebPWriter.h>
|
#include <LibGfx/ImageFormats/WebPWriter.h>
|
||||||
|
|
||||||
|
@ -50,8 +49,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
auto animation_writer = TRY([&]() -> ErrorOr<NonnullOwnPtr<Gfx::AnimationWriter>> {
|
auto animation_writer = TRY([&]() -> ErrorOr<NonnullOwnPtr<Gfx::AnimationWriter>> {
|
||||||
if (options.out_path.ends_with(".webp"sv))
|
if (options.out_path.ends_with(".webp"sv))
|
||||||
return Gfx::WebPWriter::start_encoding_animation(*output_stream, decoder->size(), decoder->loop_count());
|
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.");
|
return Error::from_string_literal("Unable to find a encoder for the requested extension.");
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
#include <LibCore/MappedFile.h>
|
#include <LibCore/MappedFile.h>
|
||||||
#include <LibGfx/ICC/Profile.h>
|
#include <LibGfx/ICC/Profile.h>
|
||||||
#include <LibGfx/ImageFormats/BMPWriter.h>
|
#include <LibGfx/ImageFormats/BMPWriter.h>
|
||||||
#include <LibGfx/ImageFormats/GIFWriter.h>
|
|
||||||
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
#include <LibGfx/ImageFormats/ImageDecoder.h>
|
||||||
#include <LibGfx/ImageFormats/JPEGWriter.h>
|
#include <LibGfx/ImageFormats/JPEGWriter.h>
|
||||||
#include <LibGfx/ImageFormats/PNGWriter.h>
|
#include <LibGfx/ImageFormats/PNGWriter.h>
|
||||||
|
@ -167,10 +166,6 @@ static ErrorOr<void> save_image(LoadedImage& image, StringView out_path, u8 jpeg
|
||||||
|
|
||||||
auto& frame = image.bitmap.get<RefPtr<Gfx::Bitmap>>();
|
auto& frame = image.bitmap.get<RefPtr<Gfx::Bitmap>>();
|
||||||
|
|
||||||
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)) {
|
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 }));
|
TRY(Gfx::JPEGWriter::encode(*TRY(stream()), *frame, { .icc_data = image.icc_data, .quality = jpeg_quality }));
|
||||||
return {};
|
return {};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue