summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGfx
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries/LibGfx')
-rw-r--r--Userland/Libraries/LibGfx/AffineTransform.cpp175
-rw-r--r--Userland/Libraries/LibGfx/AffineTransform.h81
-rw-r--r--Userland/Libraries/LibGfx/BMPLoader.cpp1400
-rw-r--r--Userland/Libraries/LibGfx/BMPLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/BMPWriter.cpp135
-rw-r--r--Userland/Libraries/LibGfx/BMPWriter.h51
-rw-r--r--Userland/Libraries/LibGfx/Bitmap.cpp495
-rw-r--r--Userland/Libraries/LibGfx/Bitmap.h336
-rw-r--r--Userland/Libraries/LibGfx/BitmapFont.cpp324
-rw-r--r--Userland/Libraries/LibGfx/BitmapFont.h150
-rw-r--r--Userland/Libraries/LibGfx/CMakeLists.txt37
-rw-r--r--Userland/Libraries/LibGfx/CharacterBitmap.cpp46
-rw-r--r--Userland/Libraries/LibGfx/CharacterBitmap.h54
-rw-r--r--Userland/Libraries/LibGfx/ClassicStylePainter.cpp405
-rw-r--r--Userland/Libraries/LibGfx/ClassicStylePainter.h49
-rw-r--r--Userland/Libraries/LibGfx/ClassicWindowTheme.cpp215
-rw-r--r--Userland/Libraries/LibGfx/ClassicWindowTheme.h63
-rw-r--r--Userland/Libraries/LibGfx/Color.cpp440
-rw-r--r--Userland/Libraries/LibGfx/Color.h333
-rw-r--r--Userland/Libraries/LibGfx/DisjointRectSet.cpp188
-rw-r--r--Userland/Libraries/LibGfx/DisjointRectSet.h159
-rw-r--r--Userland/Libraries/LibGfx/Emoji.cpp54
-rw-r--r--Userland/Libraries/LibGfx/Emoji.h40
-rw-r--r--Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h42
-rw-r--r--Userland/Libraries/LibGfx/Filters/Filter.h52
-rw-r--r--Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h173
-rw-r--r--Userland/Libraries/LibGfx/Filters/LaplacianFilter.h41
-rw-r--r--Userland/Libraries/LibGfx/Filters/SharpenFilter.h41
-rw-r--r--Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h43
-rw-r--r--Userland/Libraries/LibGfx/Font.cpp40
-rw-r--r--Userland/Libraries/LibGfx/Font.h114
-rw-r--r--Userland/Libraries/LibGfx/FontDatabase.cpp159
-rw-r--r--Userland/Libraries/LibGfx/FontDatabase.h59
-rw-r--r--Userland/Libraries/LibGfx/Forward.h72
-rw-r--r--Userland/Libraries/LibGfx/GIFLoader.cpp765
-rw-r--r--Userland/Libraries/LibGfx/GIFLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/Gamma.h147
-rw-r--r--Userland/Libraries/LibGfx/ICOLoader.cpp438
-rw-r--r--Userland/Libraries/LibGfx/ICOLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/ImageDecoder.cpp87
-rw-r--r--Userland/Libraries/LibGfx/ImageDecoder.h98
-rw-r--r--Userland/Libraries/LibGfx/JPGLoader.cpp1420
-rw-r--r--Userland/Libraries/LibGfx/JPGLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/Matrix.h104
-rw-r--r--Userland/Libraries/LibGfx/Matrix4x4.h122
-rw-r--r--Userland/Libraries/LibGfx/Orientation.h38
-rw-r--r--Userland/Libraries/LibGfx/PBMLoader.cpp225
-rw-r--r--Userland/Libraries/LibGfx/PBMLoader.h61
-rw-r--r--Userland/Libraries/LibGfx/PGMLoader.cpp228
-rw-r--r--Userland/Libraries/LibGfx/PGMLoader.h61
-rw-r--r--Userland/Libraries/LibGfx/PNGLoader.cpp1078
-rw-r--r--Userland/Libraries/LibGfx/PNGLoader.h58
-rw-r--r--Userland/Libraries/LibGfx/PPMLoader.cpp230
-rw-r--r--Userland/Libraries/LibGfx/PPMLoader.h61
-rw-r--r--Userland/Libraries/LibGfx/Painter.cpp1629
-rw-r--r--Userland/Libraries/LibGfx/Painter.h167
-rw-r--r--Userland/Libraries/LibGfx/Palette.cpp117
-rw-r--r--Userland/Libraries/LibGfx/Palette.h158
-rw-r--r--Userland/Libraries/LibGfx/Path.cpp234
-rw-r--r--Userland/Libraries/LibGfx/Path.h220
-rw-r--r--Userland/Libraries/LibGfx/Point.cpp88
-rw-r--r--Userland/Libraries/LibGfx/Point.h257
-rw-r--r--Userland/Libraries/LibGfx/PortableImageLoaderCommon.h309
-rw-r--r--Userland/Libraries/LibGfx/Rect.cpp181
-rw-r--r--Userland/Libraries/LibGfx/Rect.h471
-rw-r--r--Userland/Libraries/LibGfx/ShareableBitmap.cpp76
-rw-r--r--Userland/Libraries/LibGfx/ShareableBitmap.h64
-rw-r--r--Userland/Libraries/LibGfx/Size.cpp71
-rw-r--r--Userland/Libraries/LibGfx/Size.h179
-rw-r--r--Userland/Libraries/LibGfx/StandardCursor.h51
-rw-r--r--Userland/Libraries/LibGfx/Streamer.h78
-rw-r--r--Userland/Libraries/LibGfx/StylePainter.cpp86
-rw-r--r--Userland/Libraries/LibGfx/StylePainter.h87
-rw-r--r--Userland/Libraries/LibGfx/SystemTheme.cpp130
-rw-r--r--Userland/Libraries/LibGfx/SystemTheme.h162
-rw-r--r--Userland/Libraries/LibGfx/TextAlignment.h92
-rw-r--r--Userland/Libraries/LibGfx/TextAttributes.h41
-rw-r--r--Userland/Libraries/LibGfx/TextElision.h36
-rw-r--r--Userland/Libraries/LibGfx/Triangle.cpp42
-rw-r--r--Userland/Libraries/LibGfx/Triangle.h81
-rw-r--r--Userland/Libraries/LibGfx/Vector3.h133
-rw-r--r--Userland/Libraries/LibGfx/WindowTheme.cpp42
-rw-r--r--Userland/Libraries/LibGfx/WindowTheme.h69
83 files changed, 16870 insertions, 0 deletions
diff --git a/Userland/Libraries/LibGfx/AffineTransform.cpp b/Userland/Libraries/LibGfx/AffineTransform.cpp
new file mode 100644
index 0000000000..29236b83f0
--- /dev/null
+++ b/Userland/Libraries/LibGfx/AffineTransform.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LogStream.h>
+#include <AK/Optional.h>
+#include <LibGfx/AffineTransform.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+bool AffineTransform::is_identity() const
+{
+ return m_values[0] == 1 && m_values[1] == 0 && m_values[2] == 0 && m_values[3] == 1 && m_values[4] == 0 && m_values[5] == 0;
+}
+
+static float hypotenuse(float x, float y)
+{
+ // FIXME: This won't handle overflow :(
+ return sqrt(x * x + y * y);
+}
+
+float AffineTransform::x_scale() const
+{
+ return hypotenuse(m_values[0], m_values[1]);
+}
+
+float AffineTransform::y_scale() const
+{
+ return hypotenuse(m_values[2], m_values[3]);
+}
+
+AffineTransform& AffineTransform::scale(float sx, float sy)
+{
+ m_values[0] *= sx;
+ m_values[1] *= sx;
+ m_values[2] *= sy;
+ m_values[3] *= sy;
+ return *this;
+}
+
+AffineTransform& AffineTransform::translate(float tx, float ty)
+{
+ m_values[4] += tx * m_values[0] + ty * m_values[2];
+ m_values[5] += tx * m_values[1] + ty * m_values[3];
+ return *this;
+}
+
+AffineTransform& AffineTransform::multiply(const AffineTransform& other)
+{
+ AffineTransform result;
+ result.m_values[0] = other.a() * a() + other.b() * c();
+ result.m_values[1] = other.a() * b() + other.b() * d();
+ result.m_values[2] = other.c() * a() + other.d() * c();
+ result.m_values[3] = other.c() * b() + other.d() * d();
+ result.m_values[4] = other.e() * a() + other.f() * c() + e();
+ result.m_values[5] = other.e() * b() + other.f() * d() + f();
+ *this = result;
+ return *this;
+}
+
+AffineTransform& AffineTransform::rotate_radians(float radians)
+{
+ float sin_angle = sinf(radians);
+ float cos_angle = cosf(radians);
+ AffineTransform rotation(cos_angle, sin_angle, -sin_angle, cos_angle, 0, 0);
+ multiply(rotation);
+ return *this;
+}
+
+void AffineTransform::map(float unmapped_x, float unmapped_y, float& mapped_x, float& mapped_y) const
+{
+ mapped_x = (m_values[0] * unmapped_x + m_values[2] * unmapped_y + m_values[4]);
+ mapped_y = (m_values[1] * unmapped_x + m_values[3] * unmapped_y + m_values[5]);
+}
+
+template<>
+IntPoint AffineTransform::map(const IntPoint& point) const
+{
+ float mapped_x;
+ float mapped_y;
+ map(point.x(), point.y(), mapped_x, mapped_y);
+ return { roundf(mapped_x), roundf(mapped_y) };
+}
+
+template<>
+FloatPoint AffineTransform::map(const FloatPoint& point) const
+{
+ float mapped_x;
+ float mapped_y;
+ map(point.x(), point.y(), mapped_x, mapped_y);
+ return { mapped_x, mapped_y };
+}
+
+template<>
+IntSize AffineTransform::map(const IntSize& size) const
+{
+ return { roundf(size.width() * x_scale()), roundf(size.height() * y_scale()) };
+}
+
+template<>
+FloatSize AffineTransform::map(const FloatSize& size) const
+{
+ return { size.width() * x_scale(), size.height() * y_scale() };
+}
+
+template<typename T>
+static T smallest_of(T p1, T p2, T p3, T p4)
+{
+ return min(min(p1, p2), min(p3, p4));
+}
+
+template<typename T>
+static T largest_of(T p1, T p2, T p3, T p4)
+{
+ return max(max(p1, p2), max(p3, p4));
+}
+
+template<>
+FloatRect AffineTransform::map(const FloatRect& rect) const
+{
+ FloatPoint p1 = map(rect.top_left());
+ FloatPoint p2 = map(rect.top_right().translated(1, 0));
+ FloatPoint p3 = map(rect.bottom_right().translated(1, 1));
+ FloatPoint p4 = map(rect.bottom_left().translated(0, 1));
+ float left = smallest_of(p1.x(), p2.x(), p3.x(), p4.x());
+ float top = smallest_of(p1.y(), p2.y(), p3.y(), p4.y());
+ float right = largest_of(p1.x(), p2.x(), p3.x(), p4.x());
+ float bottom = largest_of(p1.y(), p2.y(), p3.y(), p4.y());
+ return { left, top, right - left, bottom - top };
+}
+
+template<>
+IntRect AffineTransform::map(const IntRect& rect) const
+{
+ return enclosing_int_rect(map(FloatRect(rect)));
+}
+
+const LogStream& operator<<(const LogStream& stream, const AffineTransform& value)
+{
+ if (value.is_identity())
+ return stream << "{ Identity }";
+
+ return stream << "{ "
+ << value.a() << ", "
+ << value.b() << ", "
+ << value.c() << ", "
+ << value.d() << ", "
+ << value.e() << ", "
+ << value.f() << " }";
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/AffineTransform.h b/Userland/Libraries/LibGfx/AffineTransform.h
new file mode 100644
index 0000000000..41334eb138
--- /dev/null
+++ b/Userland/Libraries/LibGfx/AffineTransform.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/LogStream.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+class AffineTransform {
+public:
+ AffineTransform()
+ : m_values { 1, 0, 0, 1, 0, 0 }
+ {
+ }
+
+ AffineTransform(float a, float b, float c, float d, float e, float f)
+ : m_values { a, b, c, d, e, f }
+ {
+ }
+
+ bool is_identity() const;
+
+ void map(float unmapped_x, float unmapped_y, float& mapped_x, float& mapped_y) const;
+
+ template<typename T>
+ Point<T> map(const Point<T>&) const;
+
+ template<typename T>
+ Size<T> map(const Size<T>&) const;
+
+ template<typename T>
+ Rect<T> map(const Rect<T>&) const;
+
+ float a() const { return m_values[0]; }
+ float b() const { return m_values[1]; }
+ float c() const { return m_values[2]; }
+ float d() const { return m_values[3]; }
+ float e() const { return m_values[4]; }
+ float f() const { return m_values[5]; }
+
+ float x_scale() const;
+ float y_scale() const;
+
+ AffineTransform& scale(float sx, float sy);
+ AffineTransform& translate(float tx, float ty);
+ AffineTransform& rotate_radians(float);
+ AffineTransform& multiply(const AffineTransform&);
+
+private:
+ float m_values[6] { 0 };
+};
+
+const LogStream& operator<<(const LogStream&, const AffineTransform&);
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPLoader.cpp b/Userland/Libraries/LibGfx/BMPLoader.cpp
new file mode 100644
index 0000000000..2b178f0257
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPLoader.cpp
@@ -0,0 +1,1400 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <LibGfx/BMPLoader.h>
+
+#ifndef BMP_DEBUG
+# define BMP_DEBUG 0
+#endif
+
+#define IF_BMP_DEBUG(x) \
+ if (BMP_DEBUG) \
+ x
+
+namespace Gfx {
+
+const u8 bmp_header_size = 14;
+const u32 color_palette_limit = 1024;
+
+// Compression flags
+struct Compression {
+ enum : u32 {
+ RGB = 0,
+ RLE8,
+ RLE4,
+ BITFIELDS,
+ RLE24, // doubles as JPEG for V4+, but that is unsupported
+ PNG,
+ ALPHABITFIELDS,
+ CMYK = 11,
+ CMYKRLE8,
+ CMYKRLE4,
+ };
+};
+
+struct DIBCore {
+ // u16 for BITMAPHEADERCORE, but i32 for everything else. If the dib type is
+ // BITMAPHEADERCORE, this is range checked.
+ i32 width;
+ i32 height;
+ u16 bpp;
+};
+
+struct DIBInfo {
+ u32 compression { Compression::RGB };
+ u32 image_size { 0 };
+ i32 horizontal_resolution { 0 };
+ i32 vertical_resolution { 0 };
+ u32 number_of_palette_colors { 0 };
+ u32 number_of_important_palette_colors { number_of_palette_colors };
+
+ // Introduced in the BITMAPV2INFOHEADER and would ideally be stored in the
+ // DIBV2 struct, however with a compression value of BI_BITFIELDS or
+ // BI_ALPHABITFIELDS, these can be specified with the Info header.
+ Vector<u32> masks;
+ Vector<i8> mask_shifts;
+ Vector<u8> mask_sizes;
+};
+
+struct DIBOSV2 {
+ u16 recording;
+ u16 halftoning;
+ u16 size1;
+ u16 size2;
+};
+
+template<typename T>
+struct Endpoint {
+ T x;
+ T y;
+ T z;
+};
+
+struct DIBV4 {
+ u32 color_space { 0 };
+ Endpoint<i32> red_endpoint { 0, 0, 0 };
+ Endpoint<i32> green_endpoint { 0, 0, 0 };
+ Endpoint<i32> blue_endpoint { 0, 0, 0 };
+ Endpoint<u32> gamma_endpoint { 0, 0, 0 };
+};
+
+struct DIBV5 {
+ u32 intent { 0 };
+ u32 profile_data { 0 };
+ u32 profile_size { 0 };
+};
+
+struct DIB {
+ DIBCore core;
+ DIBInfo info;
+ DIBOSV2 osv2;
+ DIBV4 v4;
+ DIBV5 v5;
+};
+
+enum class DIBType {
+ Core = 0,
+ OSV2Short,
+ OSV2,
+ Info,
+ V2,
+ V3,
+ V4,
+ V5
+};
+
+struct BMPLoadingContext {
+ enum class State {
+ NotDecoded = 0,
+ HeaderDecoded,
+ DIBDecoded,
+ ColorTableDecoded,
+ PixelDataDecoded,
+ Error,
+ };
+ State state { State::NotDecoded };
+
+ const u8* file_bytes { nullptr };
+ size_t file_size { 0 };
+ u32 data_offset { 0 };
+
+ DIB dib;
+ DIBType dib_type;
+
+ Vector<u32> color_table;
+ RefPtr<Gfx::Bitmap> bitmap;
+
+ u32 dib_size() const
+ {
+ switch (dib_type) {
+ case DIBType::Core:
+ return 12;
+ case DIBType::OSV2Short:
+ return 16;
+ case DIBType::OSV2:
+ return 64;
+ case DIBType::Info:
+ return 40;
+ case DIBType::V2:
+ return 52;
+ case DIBType::V3:
+ return 56;
+ case DIBType::V4:
+ return 108;
+ case DIBType::V5:
+ return 124;
+ }
+
+ ASSERT_NOT_REACHED();
+ }
+};
+
+static RefPtr<Bitmap> load_bmp_impl(const u8*, size_t);
+
+RefPtr<Gfx::Bitmap> load_bmp(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_bmp_impl((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded BMP: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_bmp_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_bmp_impl(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded BMP: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+static const LogStream& operator<<(const LogStream& out, Endpoint<i32> ep)
+{
+ return out << "(" << ep.x << ", " << ep.y << ", " << ep.z << ")";
+}
+
+static const LogStream& operator<<(const LogStream& out, Endpoint<u32> ep)
+{
+ return out << "(" << ep.x << ", " << ep.y << ", " << ep.z << ")";
+}
+
+class Streamer {
+public:
+ Streamer(const u8* data, size_t size)
+ : m_data_ptr(data)
+ , m_size_remaining(size)
+ {
+ }
+
+ u8 read_u8()
+ {
+ ASSERT(m_size_remaining >= 1);
+ m_size_remaining--;
+ return *(m_data_ptr++);
+ }
+
+ u16 read_u16()
+ {
+ return read_u8() | (read_u8() << 8);
+ }
+
+ u32 read_u24()
+ {
+ return read_u8() | (read_u8() << 8) | (read_u8() << 16);
+ }
+
+ i32 read_i32()
+ {
+ return static_cast<i32>(read_u16() | (read_u16() << 16));
+ }
+
+ u32 read_u32()
+ {
+ return read_u16() | (read_u16() << 16);
+ }
+
+ void drop_bytes(u8 num_bytes)
+ {
+ ASSERT(m_size_remaining >= num_bytes);
+ m_size_remaining -= num_bytes;
+ m_data_ptr += num_bytes;
+ }
+
+ bool at_end() const { return !m_size_remaining; }
+
+ bool has_u8() const { return m_size_remaining >= 1; }
+ bool has_u16() const { return m_size_remaining >= 2; }
+ bool has_u24() const { return m_size_remaining >= 3; }
+ bool has_u32() const { return m_size_remaining >= 4; }
+
+ size_t remaining() const { return m_size_remaining; }
+
+private:
+ const u8* m_data_ptr { nullptr };
+ size_t m_size_remaining { 0 };
+};
+
+// Lookup table for distributing all possible 2-bit numbers evenly into 8-bit numbers
+static u8 scaling_factors_2bit[4] = {
+ 0x00,
+ 0x55,
+ 0xaa,
+ 0xff,
+};
+
+// Lookup table for distributing all possible 3-bit numbers evenly into 8-bit numbers
+static u8 scaling_factors_3bit[8] = {
+ 0x00,
+ 0x24,
+ 0x48,
+ 0x6d,
+ 0x91,
+ 0xb6,
+ 0xdb,
+ 0xff,
+};
+
+static u8 scale_masked_8bit_number(u8 number, u8 bits_set)
+{
+ // If there are more than 4 bit set, an easy way to scale the number is to
+ // just copy the most significant bits into the least significant bits
+ if (bits_set >= 4)
+ return number | (number >> bits_set);
+ if (!bits_set)
+ return 0;
+ if (bits_set == 1)
+ return number ? 0xff : 0;
+ if (bits_set == 2)
+ return scaling_factors_2bit[number >> 6];
+ return scaling_factors_3bit[number >> 5];
+}
+
+static u8 get_scaled_color(u32 data, u8 mask_size, i8 mask_shift)
+{
+ // A negative mask_shift indicates we actually need to left shift
+ // the result in order to get out a valid 8-bit color (for example, the blue
+ // value in an RGB555 encoding is XXXBBBBB, which needs to be shifted to the
+ // left by 3, hence it would have a "mask_shift" value of -3).
+ if (mask_shift < 0)
+ return scale_masked_8bit_number(data << -mask_shift, mask_size);
+ return scale_masked_8bit_number(data >> mask_shift, mask_size);
+}
+
+// Scales an 8-bit number with "mask_size" bits set (and "8 - mask_size" bits
+// ignored). This function scales the number appropriately over the entire
+// 256 value color spectrum.
+// Note that a much simpler scaling can be done by simple bit shifting. If you
+// just ignore the bottom 8-mask_size bits, then you get *close*. However,
+// consider, as an example, a 5 bit number (so the bottom 3 bits are ignored).
+// The purest white you could get is 0xf8, which is 248 in RGB-land. We need
+// to scale the values in order to reach the proper value of 255.
+static u32 int_to_scaled_rgb(BMPLoadingContext& context, u32 data)
+{
+ IF_BMP_DEBUG(dbg() << "DIB info sizes before access: #masks=" << context.dib.info.masks.size() << ", #mask_sizes=" << context.dib.info.mask_sizes.size() << ", #mask_shifts=" << context.dib.info.mask_shifts.size());
+
+ u8 r = get_scaled_color(data & context.dib.info.masks[0], context.dib.info.mask_sizes[0], context.dib.info.mask_shifts[0]);
+ u8 g = get_scaled_color(data & context.dib.info.masks[1], context.dib.info.mask_sizes[1], context.dib.info.mask_shifts[1]);
+ u8 b = get_scaled_color(data & context.dib.info.masks[2], context.dib.info.mask_sizes[2], context.dib.info.mask_shifts[2]);
+ u32 color = (r << 16) | (g << 8) | b;
+
+ if (context.dib.info.masks.size() == 4) {
+ // The bitmap has an alpha mask
+ u8 a = get_scaled_color(data & context.dib.info.masks[3], context.dib.info.mask_sizes[3], context.dib.info.mask_shifts[3]);
+ color |= (a << 24);
+ } else {
+ color |= 0xff000000;
+ }
+
+ return color;
+}
+
+static void populate_dib_mask_info_if_needed(BMPLoadingContext& context)
+{
+ if (context.dib.info.masks.is_empty())
+ return;
+
+ // Mask shift is the number of right shifts needed to align the MSb of the
+ // mask to the MSb of the LSB. Note that this can be a negative number.
+ // Mask size is the number of set bits in the mask. This is required for
+ // color scaling (for example, ensuring that a 4-bit color value spans the
+ // entire 256 value color spectrum.
+ auto& masks = context.dib.info.masks;
+ auto& mask_shifts = context.dib.info.mask_shifts;
+ auto& mask_sizes = context.dib.info.mask_sizes;
+
+ if (!mask_shifts.is_empty() && !mask_sizes.is_empty())
+ return;
+
+ ASSERT(mask_shifts.is_empty() && mask_sizes.is_empty());
+
+ mask_shifts.ensure_capacity(masks.size());
+ mask_sizes.ensure_capacity(masks.size());
+
+ for (size_t i = 0; i < masks.size(); ++i) {
+ u32 mask = masks[i];
+ if (!mask) {
+ mask_shifts.append(0);
+ mask_sizes.append(0);
+ continue;
+ }
+ int trailing_zeros = count_trailing_zeroes_32(mask);
+ int size = count_trailing_zeroes_32(~(mask >> trailing_zeros));
+ mask_shifts.append(trailing_zeros - 8);
+ mask_sizes.append(size);
+ }
+}
+
+static bool check_for_invalid_bitmask_combinations(BMPLoadingContext& context)
+{
+ auto& bpp = context.dib.core.bpp;
+ auto& compression = context.dib.info.compression;
+
+ if (compression == Compression::ALPHABITFIELDS && context.dib_type != DIBType::Info)
+ return false;
+
+ switch (context.dib_type) {
+ case DIBType::Core:
+ if (bpp == 2 || bpp == 16 || bpp == 32)
+ return false;
+ break;
+ case DIBType::Info:
+ switch (compression) {
+ case Compression::BITFIELDS:
+ case Compression::ALPHABITFIELDS:
+ if (bpp != 16 && bpp != 32)
+ return false;
+ break;
+ case Compression::RGB:
+ break;
+ case Compression::RLE8:
+ if (bpp > 8)
+ return false;
+ break;
+ case Compression::RLE4:
+ // TODO: This is a guess
+ if (bpp > 4)
+ return false;
+ break;
+ default:
+ // Other compressions are not officially supported.
+ // Technically, we could even drop ALPHABITFIELDS.
+ return false;
+ }
+ break;
+ case DIBType::OSV2Short:
+ case DIBType::OSV2:
+ case DIBType::V2:
+ case DIBType::V3:
+ case DIBType::V4:
+ case DIBType::V5:
+ if (compression == Compression::BITFIELDS && bpp != 16 && bpp != 32)
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+static bool set_dib_bitmasks(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!check_for_invalid_bitmask_combinations(context))
+ return false;
+
+ auto& bpp = context.dib.core.bpp;
+ if (bpp <= 8 || bpp == 24)
+ return true;
+
+ auto& compression = context.dib.info.compression;
+ auto& type = context.dib_type;
+
+ if (type > DIBType::OSV2 && bpp == 16 && compression == Compression::RGB) {
+ context.dib.info.masks.append({ 0x7c00, 0x03e0, 0x001f });
+ context.dib.info.mask_shifts.append({ 7, 2, -3 });
+ context.dib.info.mask_sizes.append({ 5, 5, 5 });
+ } else if (type == DIBType::Info && (compression == Compression::BITFIELDS || compression == Compression::ALPHABITFIELDS)) {
+ // Consume the extra BITFIELDS bytes
+ auto number_of_mask_fields = compression == Compression::ALPHABITFIELDS ? 4 : 3;
+
+ for (auto i = 0; i < number_of_mask_fields; i++) {
+ if (!streamer.has_u32())
+ return false;
+ context.dib.info.masks.append(streamer.read_u32());
+ }
+ }
+
+ populate_dib_mask_info_if_needed(context);
+ return true;
+}
+
+static bool decode_bmp_header(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state >= BMPLoadingContext::State::HeaderDecoded)
+ return true;
+
+ if (!context.file_bytes || context.file_size < bmp_header_size) {
+ IF_BMP_DEBUG(dbg() << "Missing BMP header");
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ Streamer streamer(context.file_bytes, bmp_header_size);
+
+ u16 header = streamer.read_u16();
+ if (header != 0x4d42) {
+ IF_BMP_DEBUG(dbgprintf("BMP has invalid magic header number: %04x\n", header));
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ // The reported size of the file in the header is actually not important
+ // for decoding the file. Some specifications say that this value should
+ // be the size of the header instead, so we just rely on the known file
+ // size, instead of a possibly-correct-but-also-possibly-incorrect reported
+ // value of the file size.
+ streamer.drop_bytes(4);
+
+ // Ignore reserved bytes
+ streamer.drop_bytes(4);
+ context.data_offset = streamer.read_u32();
+
+ IF_BMP_DEBUG(dbg() << "BMP file size: " << context.file_size);
+ IF_BMP_DEBUG(dbg() << "BMP data offset: " << context.data_offset);
+
+ if (context.data_offset >= context.file_size) {
+ IF_BMP_DEBUG(dbg() << "BMP data offset is beyond file end?!");
+ return false;
+ }
+
+ context.state = BMPLoadingContext::State::HeaderDecoded;
+ return true;
+}
+
+static bool decode_bmp_core_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ auto& core = context.dib.core;
+
+ // The width and height are u16 fields in the actual BITMAPCOREHEADER format.
+ if (context.dib_type == DIBType::Core) {
+ core.width = streamer.read_u16();
+ core.height = streamer.read_u16();
+ } else {
+ core.width = streamer.read_i32();
+ core.height = streamer.read_i32();
+ }
+
+ if (core.width < 0) {
+ IF_BMP_DEBUG(dbg() << "BMP has a negative width: " << core.width);
+ return false;
+ }
+
+ if (static_cast<size_t>(core.width) > maximum_width_for_decoded_images || static_cast<size_t>(abs(core.height)) > maximum_height_for_decoded_images) {
+ dbgln("This BMP is too large for comfort: {}x{}", core.width, abs(core.height));
+ return false;
+ }
+
+ auto color_planes = streamer.read_u16();
+ if (color_planes != 1) {
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid number of color planes: " << color_planes);
+ return false;
+ }
+
+ core.bpp = streamer.read_u16();
+ switch (core.bpp) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ break;
+ default:
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid bpp: " << core.bpp);
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ IF_BMP_DEBUG(dbg() << "BMP width: " << core.width);
+ IF_BMP_DEBUG(dbg() << "BMP height: " << core.height);
+ IF_BMP_DEBUG(dbg() << "BMP bits_per_pixel: " << core.bpp);
+
+ return true;
+}
+
+ALWAYS_INLINE static bool is_supported_compression_format(BMPLoadingContext& context, u32 compression)
+{
+ return compression == Compression::RGB || compression == Compression::BITFIELDS
+ || compression == Compression::ALPHABITFIELDS || compression == Compression::RLE8
+ || compression == Compression::RLE4 || (compression == Compression::RLE24 && context.dib_type <= DIBType::OSV2);
+}
+
+static bool decode_bmp_osv2_dib(BMPLoadingContext& context, Streamer& streamer, bool short_variant = false)
+{
+ auto& core = context.dib.core;
+
+ core.width = streamer.read_u32();
+ core.height = streamer.read_u32();
+
+ if (core.width < 0) {
+ IF_BMP_DEBUG(dbg() << "BMP has a negative width: " << core.width);
+ return false;
+ }
+
+ auto color_planes = streamer.read_u16();
+ if (color_planes != 1) {
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid number of color planes: " << color_planes);
+ return false;
+ }
+
+ core.bpp = streamer.read_u16();
+ switch (core.bpp) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 24:
+ break;
+ default:
+ // OS/2 didn't expect 16- or 32-bpp to be popular.
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid bpp: " << core.bpp);
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ IF_BMP_DEBUG(dbg() << "BMP width: " << core.width);
+ IF_BMP_DEBUG(dbg() << "BMP height: " << core.height);
+ IF_BMP_DEBUG(dbg() << "BMP bpp: " << core.bpp);
+
+ if (short_variant)
+ return true;
+
+ auto& info = context.dib.info;
+ auto& osv2 = context.dib.osv2;
+
+ info.compression = streamer.read_u32();
+ info.image_size = streamer.read_u32();
+ info.horizontal_resolution = streamer.read_u32();
+ info.vertical_resolution = streamer.read_u32();
+ info.number_of_palette_colors = streamer.read_u32();
+ info.number_of_important_palette_colors = streamer.read_u32();
+
+ if (!is_supported_compression_format(context, info.compression)) {
+ IF_BMP_DEBUG(dbg() << "BMP has unsupported compression value: " << info.compression);
+ return false;
+ }
+
+ if (info.number_of_palette_colors > color_palette_limit || info.number_of_important_palette_colors > color_palette_limit) {
+ IF_BMP_DEBUG(dbg() << "BMP header indicates too many palette colors: " << info.number_of_palette_colors);
+ return false;
+ }
+
+ // Units (2) + reserved (2)
+ streamer.drop_bytes(4);
+
+ osv2.recording = streamer.read_u16();
+ osv2.halftoning = streamer.read_u16();
+ osv2.size1 = streamer.read_u32();
+ osv2.size2 = streamer.read_u32();
+
+ // ColorEncoding (4) + Identifier (4)
+ streamer.drop_bytes(8);
+
+ IF_BMP_DEBUG(dbg() << "BMP compression: " << info.compression);
+ IF_BMP_DEBUG(dbg() << "BMP image size: " << info.image_size);
+ IF_BMP_DEBUG(dbg() << "BMP horizontal res: " << info.horizontal_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP vertical res: " << info.vertical_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP colors: " << info.number_of_palette_colors);
+ IF_BMP_DEBUG(dbg() << "BMP important colors: " << info.number_of_important_palette_colors);
+
+ return true;
+}
+
+static bool decode_bmp_info_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_core_dib(context, streamer))
+ return false;
+
+ auto& info = context.dib.info;
+
+ auto compression = streamer.read_u32();
+ info.compression = compression;
+ if (!is_supported_compression_format(context, compression)) {
+ IF_BMP_DEBUG(dbg() << "BMP has unsupported compression value: " << compression);
+ return false;
+ }
+
+ info.image_size = streamer.read_u32();
+ info.horizontal_resolution = streamer.read_i32();
+ info.vertical_resolution = streamer.read_i32();
+ info.number_of_palette_colors = streamer.read_u32();
+ info.number_of_important_palette_colors = streamer.read_u32();
+
+ if (info.number_of_palette_colors > color_palette_limit || info.number_of_important_palette_colors > color_palette_limit) {
+ IF_BMP_DEBUG(dbg() << "BMP header indicates too many palette colors: " << info.number_of_palette_colors);
+ return false;
+ }
+
+ if (info.number_of_important_palette_colors == 0)
+ info.number_of_important_palette_colors = info.number_of_palette_colors;
+
+ IF_BMP_DEBUG(dbg() << "BMP compression: " << info.compression);
+ IF_BMP_DEBUG(dbg() << "BMP image size: " << info.image_size);
+ IF_BMP_DEBUG(dbg() << "BMP horizontal resolution: " << info.horizontal_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP vertical resolution: " << info.vertical_resolution);
+ IF_BMP_DEBUG(dbg() << "BMP palette colors: " << info.number_of_palette_colors);
+ IF_BMP_DEBUG(dbg() << "BMP important palette colors: " << info.number_of_important_palette_colors);
+
+ return true;
+}
+
+static bool decode_bmp_v2_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_info_dib(context, streamer))
+ return false;
+
+ context.dib.info.masks.append(streamer.read_u32());
+ context.dib.info.masks.append(streamer.read_u32());
+ context.dib.info.masks.append(streamer.read_u32());
+
+ IF_BMP_DEBUG(dbgprintf("BMP red mask: %08x\n", context.dib.info.masks[0]));
+ IF_BMP_DEBUG(dbgprintf("BMP green mask: %08x\n", context.dib.info.masks[1]));
+ IF_BMP_DEBUG(dbgprintf("BMP blue mask: %08x\n", context.dib.info.masks[2]));
+
+ return true;
+}
+
+static bool decode_bmp_v3_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_v2_dib(context, streamer))
+ return false;
+
+ // There is zero documentation about when alpha masks actually get applied.
+ // Well, there's some, but it's not even close to comprehensive. So, this is
+ // in no way based off of any spec, it's simply based off of the BMP test
+ // suite results.
+ if (context.dib.info.compression == Compression::ALPHABITFIELDS) {
+ context.dib.info.masks.append(streamer.read_u32());
+ IF_BMP_DEBUG(dbgprintf("BMP alpha mask: %08x\n", context.dib.info.masks[3]));
+ } else if (context.dib_size() >= 56 && context.dib.core.bpp >= 16) {
+ auto mask = streamer.read_u32();
+ if ((context.dib.core.bpp == 32 && mask != 0) || context.dib.core.bpp == 16) {
+ context.dib.info.masks.append(mask);
+ IF_BMP_DEBUG(dbgprintf("BMP alpha mask: %08x\n", mask));
+ }
+ } else {
+ streamer.drop_bytes(4);
+ }
+
+ return true;
+}
+
+static bool decode_bmp_v4_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_v3_dib(context, streamer))
+ return false;
+
+ auto& v4 = context.dib.v4;
+ v4.color_space = streamer.read_u32();
+ v4.red_endpoint = { streamer.read_i32(), streamer.read_i32(), streamer.read_i32() };
+ v4.green_endpoint = { streamer.read_i32(), streamer.read_i32(), streamer.read_i32() };
+ v4.blue_endpoint = { streamer.read_i32(), streamer.read_i32(), streamer.read_i32() };
+ v4.gamma_endpoint = { streamer.read_u32(), streamer.read_u32(), streamer.read_u32() };
+
+ IF_BMP_DEBUG(dbg() << "BMP color space: " << v4.color_space);
+ IF_BMP_DEBUG(dbg() << "BMP red endpoint: " << v4.red_endpoint);
+ IF_BMP_DEBUG(dbg() << "BMP green endpoint: " << v4.green_endpoint);
+ IF_BMP_DEBUG(dbg() << "BMP blue endpoint: " << v4.blue_endpoint);
+ IF_BMP_DEBUG(dbg() << "BMP gamma endpoint: " << v4.gamma_endpoint);
+
+ return true;
+}
+
+static bool decode_bmp_v5_dib(BMPLoadingContext& context, Streamer& streamer)
+{
+ if (!decode_bmp_v4_dib(context, streamer))
+ return false;
+
+ auto& v5 = context.dib.v5;
+ v5.intent = streamer.read_u32();
+ v5.profile_data = streamer.read_u32();
+ v5.profile_size = streamer.read_u32();
+
+ IF_BMP_DEBUG(dbg() << "BMP intent: " << v5.intent);
+ IF_BMP_DEBUG(dbg() << "BMP profile data: " << v5.profile_data);
+ IF_BMP_DEBUG(dbg() << "BMP profile size: " << v5.profile_size);
+
+ return true;
+}
+
+static bool decode_bmp_dib(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state >= BMPLoadingContext::State::DIBDecoded)
+ return true;
+
+ if (context.state < BMPLoadingContext::State::HeaderDecoded && !decode_bmp_header(context))
+ return false;
+
+ if (context.file_size < bmp_header_size + 4)
+ return false;
+
+ Streamer streamer(context.file_bytes + bmp_header_size, 4);
+ u32 dib_size = streamer.read_u32();
+
+ if (context.file_size < bmp_header_size + dib_size)
+ return false;
+ if (context.data_offset < bmp_header_size + dib_size) {
+ IF_BMP_DEBUG(dbg() << "Shenanigans! BMP pixel data and header usually don't overlap.");
+ return false;
+ }
+
+ streamer = Streamer(context.file_bytes + bmp_header_size + 4, context.data_offset - bmp_header_size - 4);
+
+ IF_BMP_DEBUG(dbg() << "BMP dib size: " << dib_size);
+
+ bool error = false;
+
+ if (dib_size == 12) {
+ context.dib_type = DIBType::Core;
+ if (!decode_bmp_core_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 64) {
+ context.dib_type = DIBType::OSV2;
+ if (!decode_bmp_osv2_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 16) {
+ context.dib_type = DIBType::OSV2Short;
+ if (!decode_bmp_osv2_dib(context, streamer, true))
+ error = true;
+ } else if (dib_size == 40) {
+ context.dib_type = DIBType::Info;
+ if (!decode_bmp_info_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 52) {
+ context.dib_type = DIBType::V2;
+ if (!decode_bmp_v2_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 56) {
+ context.dib_type = DIBType::V3;
+ if (!decode_bmp_v3_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 108) {
+ context.dib_type = DIBType::V4;
+ if (!decode_bmp_v4_dib(context, streamer))
+ error = true;
+ } else if (dib_size == 124) {
+ context.dib_type = DIBType::V5;
+ if (!decode_bmp_v5_dib(context, streamer))
+ error = true;
+ } else {
+ IF_BMP_DEBUG(dbg() << "Unsupported BMP DIB size: " << dib_size);
+ error = true;
+ }
+
+ switch (context.dib.info.compression) {
+ case Compression::RGB:
+ case Compression::RLE8:
+ case Compression::RLE4:
+ case Compression::BITFIELDS:
+ case Compression::RLE24:
+ case Compression::PNG:
+ case Compression::ALPHABITFIELDS:
+ case Compression::CMYK:
+ case Compression::CMYKRLE8:
+ case Compression::CMYKRLE4:
+ break;
+ default:
+ error = true;
+ }
+
+ if (!error && !set_dib_bitmasks(context, streamer))
+ error = true;
+
+ if (error) {
+ IF_BMP_DEBUG(dbg() << "BMP has an invalid DIB");
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ context.state = BMPLoadingContext::State::DIBDecoded;
+
+ return true;
+}
+
+static bool decode_bmp_color_table(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state < BMPLoadingContext::State::DIBDecoded && !decode_bmp_dib(context))
+ return false;
+
+ if (context.state >= BMPLoadingContext::State::ColorTableDecoded)
+ return true;
+
+ if (context.dib.core.bpp > 8) {
+ context.state = BMPLoadingContext::State::ColorTableDecoded;
+ return true;
+ }
+
+ auto bytes_per_color = context.dib_type == DIBType::Core ? 3 : 4;
+ u32 max_colors = 1 << context.dib.core.bpp;
+ ASSERT(context.data_offset >= bmp_header_size + context.dib_size());
+ auto size_of_color_table = context.data_offset - bmp_header_size - context.dib_size();
+
+ if (context.dib_type <= DIBType::OSV2) {
+ // Partial color tables are not supported, so the space of the color
+ // table must be at least enough for the maximum amount of colors
+ if (size_of_color_table < 3 * max_colors) {
+ // This is against the spec, but most viewers process it anyways
+ IF_BMP_DEBUG(dbg() << "BMP with CORE header does not have enough colors. Has: " << size_of_color_table << ", expected: " << (3 * max_colors));
+ }
+ }
+
+ Streamer streamer(context.file_bytes + bmp_header_size + context.dib_size(), size_of_color_table);
+ for (u32 i = 0; !streamer.at_end() && i < max_colors; ++i) {
+ if (bytes_per_color == 4) {
+ if (!streamer.has_u32())
+ return false;
+ context.color_table.append(streamer.read_u32());
+ } else {
+ if (!streamer.has_u24())
+ return false;
+ context.color_table.append(streamer.read_u24());
+ }
+ }
+
+ context.state = BMPLoadingContext::State::ColorTableDecoded;
+ return true;
+}
+
+struct RLEState {
+ enum : u8 {
+ PixelCount = 0,
+ PixelValue,
+ Meta, // Represents just consuming a null byte, which indicates something special
+ };
+};
+
+static bool uncompress_bmp_rle_data(BMPLoadingContext& context, ByteBuffer& buffer)
+{
+ // RLE-compressed images cannot be stored top-down
+ if (context.dib.core.height < 0) {
+ IF_BMP_DEBUG(dbg() << "BMP is top-down and RLE compressed");
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ Streamer streamer(context.file_bytes + context.data_offset, context.file_size - context.data_offset);
+
+ auto compression = context.dib.info.compression;
+
+ u32 total_rows = static_cast<u32>(context.dib.core.height);
+ u32 total_columns = round_up_to_power_of_two(static_cast<u32>(context.dib.core.width), 4);
+ u32 column = 0;
+ u32 row = 0;
+ auto currently_consuming = RLEState::PixelCount;
+ i16 pixel_count = 0;
+
+ // ByteBuffer asserts that allocating the memory never fails.
+ // FIXME: ByteBuffer should return either RefPtr<> or Optional<>.
+ // Decoding the RLE data on-the-fly might actually be faster, and avoids this topic entirely.
+ u32 buffer_size;
+ if (compression == Compression::RLE24) {
+ buffer_size = total_rows * round_up_to_power_of_two(total_columns, 4) * 4;
+ } else {
+ buffer_size = total_rows * round_up_to_power_of_two(total_columns, 4);
+ }
+ if (buffer_size > 300 * MiB) {
+ IF_BMP_DEBUG(dbg() << "Suspiciously large amount of RLE data");
+ return false;
+ }
+ buffer = ByteBuffer::create_zeroed(buffer_size);
+
+ // Avoid as many if statements as possible by pulling out
+ // compression-dependent actions into separate lambdas
+ Function<u32()> get_buffer_index;
+ Function<bool(u32, bool)> set_byte;
+ Function<Optional<u32>()> read_byte;
+
+ if (compression == Compression::RLE8) {
+ get_buffer_index = [&]() -> u32 { return row * total_columns + column; };
+ } else if (compression == Compression::RLE4) {
+ get_buffer_index = [&]() -> u32 { return (row * total_columns + column) / 2; };
+ } else {
+ get_buffer_index = [&]() -> u32 { return (row * total_columns + column) * 3; };
+ }
+
+ if (compression == Compression::RLE8) {
+ set_byte = [&](u32 color, bool) -> bool {
+ if (column >= total_columns) {
+ column = 0;
+ row++;
+ }
+ auto index = get_buffer_index();
+ if (index >= buffer.size()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return false;
+ }
+ buffer[index] = color;
+ column++;
+ return true;
+ };
+ } else if (compression == Compression::RLE24) {
+ set_byte = [&](u32 color, bool) -> bool {
+ if (column >= total_columns) {
+ column = 0;
+ row++;
+ }
+ auto index = get_buffer_index();
+ if (index + 3 >= buffer.size()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return false;
+ }
+ ((u32&)buffer[index]) = color;
+ column++;
+ return true;
+ };
+ } else {
+ set_byte = [&](u32 byte, bool rle4_set_second_nibble) -> bool {
+ if (column >= total_columns) {
+ column = 0;
+ row++;
+ }
+
+ u32 index = get_buffer_index();
+ if (index >= buffer.size() || (rle4_set_second_nibble && index + 1 >= buffer.size())) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return false;
+ }
+
+ if (column % 2) {
+ buffer[index] |= byte >> 4;
+ if (rle4_set_second_nibble) {
+ buffer[index + 1] |= byte << 4;
+ column++;
+ }
+ } else {
+ if (rle4_set_second_nibble) {
+ buffer[index] = byte;
+ column++;
+ } else {
+ buffer[index] |= byte & 0xf0;
+ }
+ }
+
+ column++;
+ return true;
+ };
+ }
+
+ if (compression == Compression::RLE24) {
+ read_byte = [&]() -> Optional<u32> {
+ if (!streamer.has_u24()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return {};
+ }
+ return streamer.read_u24();
+ };
+ } else {
+ read_byte = [&]() -> Optional<u32> {
+ if (!streamer.has_u8()) {
+ IF_BMP_DEBUG(dbg() << "BMP has badly-formatted RLE data");
+ return {};
+ }
+ return streamer.read_u8();
+ };
+ }
+
+ while (true) {
+ u32 byte;
+
+ switch (currently_consuming) {
+ case RLEState::PixelCount:
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ if (!byte) {
+ currently_consuming = RLEState::Meta;
+ } else {
+ pixel_count = byte;
+ currently_consuming = RLEState::PixelValue;
+ }
+ break;
+ case RLEState::PixelValue: {
+ auto result = read_byte();
+ if (!result.has_value())
+ return false;
+ byte = result.value();
+ for (u16 i = 0; i < pixel_count; ++i) {
+ if (compression != Compression::RLE4) {
+ if (!set_byte(byte, true))
+ return false;
+ } else {
+ if (!set_byte(byte, i != pixel_count - 1))
+ return false;
+ i++;
+ }
+ }
+
+ currently_consuming = RLEState::PixelCount;
+ break;
+ }
+ case RLEState::Meta:
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ if (!byte) {
+ column = 0;
+ row++;
+ currently_consuming = RLEState::PixelCount;
+ continue;
+ }
+ if (byte == 1)
+ return true;
+ if (byte == 2) {
+ if (!streamer.has_u8())
+ return false;
+ u8 offset_x = streamer.read_u8();
+ if (!streamer.has_u8())
+ return false;
+ u8 offset_y = streamer.read_u8();
+ column += offset_x;
+ if (column >= total_columns) {
+ column -= total_columns;
+ row++;
+ }
+ row += offset_y;
+ currently_consuming = RLEState::PixelCount;
+ continue;
+ }
+
+ // Consume literal bytes
+ pixel_count = byte;
+ i16 i = byte;
+
+ while (i >= 1) {
+ auto result = read_byte();
+ if (!result.has_value())
+ return false;
+ byte = result.value();
+ if (!set_byte(byte, i != 1))
+ return false;
+ i--;
+ if (compression == Compression::RLE4)
+ i--;
+ }
+
+ // Optionally consume a padding byte
+ if (compression != Compression::RLE4) {
+ if (pixel_count % 2) {
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ }
+ } else {
+ if (((pixel_count + 1) / 2) % 2) {
+ if (!streamer.has_u8())
+ return false;
+ byte = streamer.read_u8();
+ }
+ }
+ currently_consuming = RLEState::PixelCount;
+ break;
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+static bool decode_bmp_pixel_data(BMPLoadingContext& context)
+{
+ if (context.state == BMPLoadingContext::State::Error)
+ return false;
+
+ if (context.state <= BMPLoadingContext::State::ColorTableDecoded && !decode_bmp_color_table(context))
+ return false;
+
+ const u16 bits_per_pixel = context.dib.core.bpp;
+
+ BitmapFormat format = [&]() -> BitmapFormat {
+ switch (bits_per_pixel) {
+ case 1:
+ return BitmapFormat::Indexed1;
+ case 2:
+ return BitmapFormat::Indexed2;
+ case 4:
+ return BitmapFormat::Indexed4;
+ case 8:
+ return BitmapFormat::Indexed8;
+ case 16:
+ if (context.dib.info.masks.size() == 4)
+ return BitmapFormat::RGBA32;
+ return BitmapFormat::RGB32;
+ case 24:
+ return BitmapFormat::RGB32;
+ case 32:
+ return BitmapFormat::RGBA32;
+ default:
+ return BitmapFormat::Invalid;
+ }
+ }();
+
+ if (format == BitmapFormat::Invalid) {
+ IF_BMP_DEBUG(dbg() << "BMP has invalid bpp of " << bits_per_pixel);
+ context.state = BMPLoadingContext::State::Error;
+ return false;
+ }
+
+ const u32 width = abs(context.dib.core.width);
+ const u32 height = abs(context.dib.core.height);
+ context.bitmap = Bitmap::create_purgeable(format, { static_cast<int>(width), static_cast<int>(height) });
+ if (!context.bitmap) {
+ IF_BMP_DEBUG(dbg() << "BMP appears to have overly large dimensions");
+ return false;
+ }
+
+ ByteBuffer rle_buffer;
+ ReadonlyBytes bytes { context.file_bytes + context.data_offset, context.file_size - context.data_offset };
+
+ if (context.dib.info.compression == Compression::RLE4 || context.dib.info.compression == Compression::RLE8
+ || context.dib.info.compression == Compression::RLE24) {
+ if (!uncompress_bmp_rle_data(context, rle_buffer))
+ return false;
+ bytes = rle_buffer.bytes();
+ }
+
+ Streamer streamer(bytes.data(), bytes.size());
+
+ auto process_row = [&](u32 row) -> bool {
+ u32 space_remaining_before_consuming_row = streamer.remaining();
+
+ for (u32 column = 0; column < width;) {
+ switch (bits_per_pixel) {
+ case 1: {
+ if (!streamer.has_u8())
+ return false;
+ u8 byte = streamer.read_u8();
+ u8 mask = 8;
+ while (column < width && mask > 0) {
+ mask -= 1;
+ context.bitmap->scanline_u8(row)[column++] = (byte >> mask) & 0x1;
+ }
+ break;
+ }
+ case 2: {
+ if (!streamer.has_u8())
+ return false;
+ u8 byte = streamer.read_u8();
+ u8 mask = 8;
+ while (column < width && mask > 0) {
+ mask -= 2;
+ context.bitmap->scanline_u8(row)[column++] = (byte >> mask) & 0x3;
+ }
+ break;
+ }
+ case 4: {
+ if (!streamer.has_u8())
+ return false;
+ u8 byte = streamer.read_u8();
+ context.bitmap->scanline_u8(row)[column++] = (byte >> 4) & 0xf;
+ if (column < width)
+ context.bitmap->scanline_u8(row)[column++] = byte & 0xf;
+ break;
+ }
+ case 8:
+ if (!streamer.has_u8())
+ return false;
+ context.bitmap->scanline_u8(row)[column++] = streamer.read_u8();
+ break;
+ case 16: {
+ if (!streamer.has_u16())
+ return false;
+ context.bitmap->scanline(row)[column++] = int_to_scaled_rgb(context, streamer.read_u16());
+ break;
+ }
+ case 24: {
+ if (!streamer.has_u24())
+ return false;
+ context.bitmap->scanline(row)[column++] = streamer.read_u24();
+ break;
+ }
+ case 32:
+ if (!streamer.has_u32())
+ return false;
+ if (context.dib.info.masks.is_empty()) {
+ context.bitmap->scanline(row)[column++] = streamer.read_u32() | 0xff000000;
+ } else {
+ context.bitmap->scanline(row)[column++] = int_to_scaled_rgb(context, streamer.read_u32());
+ }
+ break;
+ }
+ }
+
+ auto consumed = space_remaining_before_consuming_row - streamer.remaining();
+
+ // Calculate padding
+ u8 bytes_to_drop = [consumed]() -> u8 {
+ switch (consumed % 4) {
+ case 0:
+ return 0;
+ case 1:
+ return 3;
+ case 2:
+ return 2;
+ case 3:
+ return 1;
+ }
+ ASSERT_NOT_REACHED();
+ }();
+ if (streamer.remaining() < bytes_to_drop)
+ return false;
+ streamer.drop_bytes(bytes_to_drop);
+
+ return true;
+ };
+
+ if (context.dib.core.height < 0) {
+ // BMP is stored top-down
+ for (u32 row = 0; row < height; ++row) {
+ if (!process_row(row))
+ return false;
+ }
+ } else {
+ for (i32 row = height - 1; row >= 0; --row) {
+ if (!process_row(row))
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < context.color_table.size(); ++i)
+ context.bitmap->set_palette_color(i, Color::from_rgb(context.color_table[i]));
+
+ context.state = BMPLoadingContext::State::PixelDataDecoded;
+
+ return true;
+}
+
+static RefPtr<Bitmap> load_bmp_impl(const u8* data, size_t data_size)
+{
+ BMPLoadingContext context;
+ context.file_bytes = data;
+ context.file_size = data_size;
+
+ // Forces a decode of the header, dib, and color table as well
+ if (!decode_bmp_pixel_data(context)) {
+ context.state = BMPLoadingContext::State::Error;
+ return nullptr;
+ }
+
+ return context.bitmap;
+}
+
+BMPImageDecoderPlugin::BMPImageDecoderPlugin(const u8* data, size_t data_size)
+{
+ m_context = make<BMPLoadingContext>();
+ m_context->file_bytes = data;
+ m_context->file_size = data_size;
+}
+
+BMPImageDecoderPlugin::~BMPImageDecoderPlugin()
+{
+}
+
+IntSize BMPImageDecoderPlugin::size()
+{
+ if (m_context->state == BMPLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < BMPLoadingContext::State::DIBDecoded && !decode_bmp_dib(*m_context))
+ return {};
+
+ return { m_context->dib.core.width, abs(m_context->dib.core.height) };
+}
+
+RefPtr<Gfx::Bitmap> BMPImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == BMPLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < BMPLoadingContext::State::PixelDataDecoded && !decode_bmp_pixel_data(*m_context))
+ return nullptr;
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void BMPImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool BMPImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool BMPImageDecoderPlugin::sniff()
+{
+ return decode_bmp_header(*m_context);
+}
+
+bool BMPImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t BMPImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t BMPImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor BMPImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0)
+ return { bitmap(), 0 };
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPLoader.h b/Userland/Libraries/LibGfx/BMPLoader.h
new file mode 100644
index 0000000000..92f54e6b5f
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_bmp(const StringView& path);
+RefPtr<Gfx::Bitmap> load_bmp_from_memory(const u8*, size_t);
+
+struct BMPLoadingContext;
+
+class BMPImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~BMPImageDecoderPlugin() override;
+ BMPImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<BMPLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPWriter.cpp b/Userland/Libraries/LibGfx/BMPWriter.cpp
new file mode 100644
index 0000000000..8ae2b00a8b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPWriter.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Vector.h>
+#include <LibGfx/BMPWriter.h>
+#include <LibGfx/Bitmap.h>
+#include <cstring>
+
+namespace Gfx {
+
+constexpr int bytes_per_pixel = 3;
+
+#define FILE_HEADER_SIZE 14
+#define IMAGE_INFORMATION_SIZE 40
+#define PIXEL_DATA_OFFSET FILE_HEADER_SIZE + IMAGE_INFORMATION_SIZE
+
+class Streamer {
+public:
+ Streamer(u8* data)
+ : m_data(data)
+ {
+ }
+
+ void write_u8(u8 i)
+ {
+ *(m_data++) = i;
+ }
+
+ void write_u16(u16 i)
+ {
+ *(m_data++) = i & 0xFF;
+ *(m_data++) = (i >> 8) & 0xFF;
+ }
+
+ void write_u32(u32 i)
+ {
+ write_u16(i & 0xFFFF);
+ write_u16((i >> 16) & 0xFFFF);
+ }
+
+ void write_i32(i32 i)
+ {
+ write_u32(static_cast<u32>(i));
+ }
+
+private:
+ u8* m_data;
+};
+
+static ByteBuffer write_pixel_data(const RefPtr<Bitmap> bitmap, int pixel_row_data_size)
+{
+ int image_size = pixel_row_data_size * bitmap->height();
+ auto buffer = ByteBuffer::create_uninitialized(image_size);
+
+ int current_row = 0;
+ for (int y = bitmap->height() - 1; y >= 0; --y) {
+ auto* row = buffer.data() + (pixel_row_data_size * current_row++);
+ for (int x = 0; x < bitmap->width(); x++) {
+ auto pixel = bitmap->get_pixel(x, y);
+ row[x * bytes_per_pixel + 0] = pixel.blue();
+ row[x * bytes_per_pixel + 1] = pixel.green();
+ row[x * bytes_per_pixel + 2] = pixel.red();
+ }
+ }
+
+ return buffer;
+}
+
+static ByteBuffer compress_pixel_data(const ByteBuffer& pixel_data, BMPWriter::Compression compression)
+{
+ switch (compression) {
+ case BMPWriter::Compression::RGB:
+ return pixel_data;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+ByteBuffer BMPWriter::dump(const RefPtr<Bitmap> bitmap)
+{
+ int pixel_row_data_size = (bytes_per_pixel * 8 * bitmap->width() + 31) / 32 * 4;
+ int image_size = pixel_row_data_size * bitmap->height();
+ auto buffer = ByteBuffer::create_uninitialized(PIXEL_DATA_OFFSET);
+
+ auto pixel_data = write_pixel_data(bitmap, pixel_row_data_size);
+ pixel_data = compress_pixel_data(pixel_data, m_compression);
+
+ int file_size = PIXEL_DATA_OFFSET + pixel_data.size();
+ Streamer streamer(buffer.data());
+ streamer.write_u8('B');
+ streamer.write_u8('M');
+ streamer.write_u32(file_size);
+ streamer.write_u32(0);
+ streamer.write_u32(PIXEL_DATA_OFFSET);
+
+ streamer.write_u32(IMAGE_INFORMATION_SIZE); // Header size
+ streamer.write_i32(bitmap->width()); // ImageWidth
+ streamer.write_i32(bitmap->height()); // ImageHeight
+ streamer.write_u16(1); // Planes
+ streamer.write_u16(bytes_per_pixel * 8); // BitsPerPixel
+ streamer.write_u32((u32)m_compression); // Compression
+ streamer.write_u32(image_size); // ImageSize
+ streamer.write_i32(0); // XpixelsPerMeter
+ streamer.write_i32(0); // YpixelsPerMeter
+ streamer.write_u32(0); // TotalColors
+ streamer.write_u32(0); // ImportantColors
+
+ buffer.append(pixel_data.data(), pixel_data.size());
+ return buffer;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BMPWriter.h b/Userland/Libraries/LibGfx/BMPWriter.h
new file mode 100644
index 0000000000..d881ab5f9d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BMPWriter.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+
+namespace Gfx {
+
+class Bitmap;
+
+class BMPWriter {
+public:
+ BMPWriter() = default;
+
+ ByteBuffer dump(const RefPtr<Bitmap>);
+
+ enum class Compression : u32 {
+ RGB = 0,
+ };
+
+ inline void set_compression(Compression compression) { m_compression = compression; }
+
+private:
+ Compression m_compression { Compression::RGB };
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp
new file mode 100644
index 0000000000..7c031f59be
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Bitmap.cpp
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Checked.h>
+#include <AK/Memory.h>
+#include <AK/MemoryStream.h>
+#include <AK/Optional.h>
+#include <AK/SharedBuffer.h>
+#include <AK/String.h>
+#include <LibGfx/BMPLoader.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/GIFLoader.h>
+#include <LibGfx/ICOLoader.h>
+#include <LibGfx/JPGLoader.h>
+#include <LibGfx/PBMLoader.h>
+#include <LibGfx/PGMLoader.h>
+#include <LibGfx/PNGLoader.h>
+#include <LibGfx/PPMLoader.h>
+#include <LibGfx/ShareableBitmap.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/mman.h>
+
+namespace Gfx {
+
+struct BackingStore {
+ void* data { nullptr };
+ size_t pitch { 0 };
+ size_t size_in_bytes { 0 };
+};
+
+size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
+{
+ size_t element_size;
+ switch (determine_storage_format(format)) {
+ case StorageFormat::Indexed8:
+ element_size = 1;
+ break;
+ case StorageFormat::RGB32:
+ case StorageFormat::RGBA32:
+ element_size = 4;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ return width * element_size;
+}
+
+static bool size_would_overflow(BitmapFormat format, const IntSize& size)
+{
+ if (size.width() < 0 || size.height() < 0)
+ return true;
+ // This check is a bit arbitrary, but should protect us from most shenanigans:
+ if (size.width() >= 32768 || size.height() >= 32768)
+ return true;
+ // In contrast, this check is absolutely necessary:
+ size_t pitch = Bitmap::minimum_pitch(size.width(), format);
+ return Checked<size_t>::multiplication_would_overflow(pitch, size.height());
+}
+
+RefPtr<Bitmap> Bitmap::create(BitmapFormat format, const IntSize& size)
+{
+ auto backing_store = Bitmap::allocate_backing_store(format, size, Purgeable::No);
+ if (!backing_store.has_value())
+ return nullptr;
+ return adopt(*new Bitmap(format, size, Purgeable::No, backing_store.value()));
+}
+
+RefPtr<Bitmap> Bitmap::create_purgeable(BitmapFormat format, const IntSize& size)
+{
+ auto backing_store = Bitmap::allocate_backing_store(format, size, Purgeable::Yes);
+ if (!backing_store.has_value())
+ return nullptr;
+ return adopt(*new Bitmap(format, size, Purgeable::Yes, backing_store.value()));
+}
+
+RefPtr<Bitmap> Bitmap::create_shareable(BitmapFormat format, const IntSize& size)
+{
+ if (size_would_overflow(format, size))
+ return nullptr;
+
+ const auto pitch = minimum_pitch(size.width(), format);
+ const auto data_size = size_in_bytes(pitch, size.height());
+ auto shared_buffer = SharedBuffer::create_with_size(data_size);
+ if (!shared_buffer)
+ return nullptr;
+ return adopt(*new Bitmap(format, shared_buffer.release_nonnull(), size, Vector<RGBA32>()));
+}
+
+Bitmap::Bitmap(BitmapFormat format, const IntSize& size, Purgeable purgeable, const BackingStore& backing_store)
+ : m_size(size)
+ , m_data(backing_store.data)
+ , m_pitch(backing_store.pitch)
+ , m_format(format)
+ , m_purgeable(purgeable == Purgeable::Yes)
+{
+ ASSERT(!m_size.is_empty());
+ ASSERT(!size_would_overflow(format, size));
+ ASSERT(m_data);
+ ASSERT(backing_store.size_in_bytes == size_in_bytes());
+ allocate_palette_from_format(format, {});
+ m_needs_munmap = true;
+}
+
+RefPtr<Bitmap> Bitmap::create_wrapper(BitmapFormat format, const IntSize& size, size_t pitch, void* data)
+{
+ if (size_would_overflow(format, size))
+ return nullptr;
+ return adopt(*new Bitmap(format, size, pitch, data));
+}
+
+RefPtr<Bitmap> Bitmap::load_from_file(const StringView& path)
+{
+#define __ENUMERATE_IMAGE_FORMAT(Name, Ext) \
+ if (path.ends_with(Ext, CaseSensitivity::CaseInsensitive)) \
+ return load_##Name(path);
+ ENUMERATE_IMAGE_FORMATS
+#undef __ENUMERATE_IMAGE_FORMAT
+
+ return nullptr;
+}
+
+Bitmap::Bitmap(BitmapFormat format, const IntSize& size, size_t pitch, void* data)
+ : m_size(size)
+ , m_data(data)
+ , m_pitch(pitch)
+ , m_format(format)
+{
+ ASSERT(pitch >= minimum_pitch(size.width(), format));
+ ASSERT(!size_would_overflow(format, size));
+ // FIXME: assert that `data` is actually long enough!
+
+ allocate_palette_from_format(format, {});
+}
+
+RefPtr<Bitmap> Bitmap::create_with_shared_buffer(BitmapFormat format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const IntSize& size)
+{
+ return create_with_shared_buffer(format, move(shared_buffer), size, {});
+}
+
+static bool check_size(const IntSize& size, BitmapFormat format, unsigned actual_size)
+{
+
+ // FIXME: Code duplication of size_in_bytes() and m_pitch
+ unsigned expected_size_min = Bitmap::minimum_pitch(size.width(), format) * size.height();
+ unsigned expected_size_max = round_up_to_power_of_two(expected_size_min, PAGE_SIZE);
+ if (expected_size_min > actual_size || actual_size > expected_size_max) {
+ // Getting here is most likely an error.
+ dbgln("Constructing a shared bitmap for format {} and size {}, which demands {} bytes, which rounds up to at most {}.",
+ static_cast<int>(format),
+ size,
+ expected_size_min,
+ expected_size_max);
+
+ dbgln("However, we were given {} bytes, which is outside this range?! Refusing cowardly.", actual_size);
+ return false;
+ }
+ return true;
+}
+
+RefPtr<Bitmap> Bitmap::create_with_shared_buffer(BitmapFormat format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const IntSize& size, const Vector<RGBA32>& palette)
+{
+ if (size_would_overflow(format, size))
+ return nullptr;
+
+ if (!check_size(size, format, shared_buffer->size()))
+ return {};
+
+ return adopt(*new Bitmap(format, move(shared_buffer), size, palette));
+}
+
+/// Read a bitmap as described by:
+/// - actual size
+/// - width
+/// - height
+/// - format
+/// - palette count
+/// - palette data (= palette count * RGBA32)
+/// - image data (= actual size * u8)
+RefPtr<Bitmap> Bitmap::create_from_serialized_byte_buffer(ByteBuffer&& buffer)
+{
+ InputMemoryStream stream { buffer };
+ unsigned actual_size;
+ unsigned width;
+ unsigned height;
+ BitmapFormat format;
+ unsigned palette_size;
+ Vector<RGBA32> palette;
+
+ auto read = [&]<typename T>(T& value) {
+ if (stream.read({ &value, sizeof(T) }) != sizeof(T))
+ return false;
+ return true;
+ };
+
+ if (!read(actual_size) || !read(width) || !read(height) || !read(format) || !read(palette_size))
+ return nullptr;
+
+ if (format > BitmapFormat::RGBA32 || format < BitmapFormat::Indexed1)
+ return nullptr;
+
+ if (!check_size({ width, height }, format, actual_size))
+ return {};
+
+ palette.ensure_capacity(palette_size);
+ for (size_t i = 0; i < palette_size; ++i) {
+ if (!read(palette[i]))
+ return {};
+ }
+
+ if (stream.remaining() < actual_size)
+ return {};
+
+ auto data = stream.bytes().slice(stream.offset(), actual_size);
+
+ auto bitmap = Bitmap::create(format, { width, height });
+ if (!bitmap)
+ return {};
+
+ bitmap->m_palette = new RGBA32[palette_size];
+ memcpy(bitmap->m_palette, palette.data(), palette_size * sizeof(RGBA32));
+
+ data.copy_to({ bitmap->scanline(0), bitmap->size_in_bytes() });
+
+ return bitmap;
+}
+
+ByteBuffer Bitmap::serialize_to_byte_buffer() const
+{
+ auto buffer = ByteBuffer::create_uninitialized(4 * sizeof(unsigned) + sizeof(BitmapFormat) + sizeof(RGBA32) * palette_size(m_format) + size_in_bytes());
+ OutputMemoryStream stream { buffer };
+
+ auto write = [&]<typename T>(T value) {
+ if (stream.write({ &value, sizeof(T) }) != sizeof(T))
+ return false;
+ return true;
+ };
+
+ auto palette = palette_to_vector();
+
+ if (!write(size_in_bytes()) || !write((unsigned)size().width()) || !write((unsigned)size().height()) || !write(m_format) || !write((unsigned)palette.size()))
+ return {};
+
+ for (auto& p : palette) {
+ if (!write(p))
+ return {};
+ }
+
+ auto size = size_in_bytes();
+ ASSERT(stream.remaining() == size);
+ if (stream.write({ scanline(0), size }) != size)
+ return {};
+
+ return buffer;
+}
+
+Bitmap::Bitmap(BitmapFormat format, NonnullRefPtr<SharedBuffer>&& shared_buffer, const IntSize& size, const Vector<RGBA32>& palette)
+ : m_size(size)
+ , m_data(shared_buffer->data<void>())
+ , m_pitch(minimum_pitch(size.width(), format))
+ , m_format(format)
+ , m_shared_buffer(move(shared_buffer))
+{
+ ASSERT(!is_indexed() || !palette.is_empty());
+ ASSERT(!size_would_overflow(format, size));
+ ASSERT(size_in_bytes() <= static_cast<size_t>(m_shared_buffer->size()));
+
+ if (is_indexed(m_format))
+ allocate_palette_from_format(m_format, palette);
+}
+
+RefPtr<Gfx::Bitmap> Bitmap::clone() const
+{
+ RefPtr<Gfx::Bitmap> new_bitmap {};
+ if (m_purgeable) {
+ new_bitmap = Bitmap::create_purgeable(format(), size());
+ } else {
+ new_bitmap = Bitmap::create(format(), size());
+ }
+
+ if (!new_bitmap) {
+ return nullptr;
+ }
+
+ ASSERT(size_in_bytes() == new_bitmap->size_in_bytes());
+ memcpy(new_bitmap->scanline(0), scanline(0), size_in_bytes());
+
+ return new_bitmap;
+}
+
+RefPtr<Gfx::Bitmap> Bitmap::rotated(Gfx::RotationDirection rotation_direction) const
+{
+ auto w = this->width();
+ auto h = this->height();
+
+ auto new_bitmap = Gfx::Bitmap::create(this->format(), { h, w });
+ if (!new_bitmap)
+ return nullptr;
+
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ Color color;
+ if (rotation_direction == Gfx::RotationDirection::Left)
+ color = this->get_pixel(w - i - 1, j);
+ else
+ color = this->get_pixel(i, h - j - 1);
+
+ new_bitmap->set_pixel(j, i, color);
+ }
+ }
+
+ return new_bitmap;
+}
+
+RefPtr<Gfx::Bitmap> Bitmap::flipped(Gfx::Orientation orientation) const
+{
+ auto w = this->width();
+ auto h = this->height();
+
+ auto new_bitmap = Gfx::Bitmap::create(this->format(), { w, h });
+ if (!new_bitmap)
+ return nullptr;
+
+ for (int i = 0; i < w; i++) {
+ for (int j = 0; j < h; j++) {
+ Color color = this->get_pixel(i, j);
+ if (orientation == Orientation::Vertical)
+ new_bitmap->set_pixel(i, h - j - 1, color);
+ else
+ new_bitmap->set_pixel(w - i - 1, j, color);
+ }
+ }
+
+ return new_bitmap;
+}
+
+RefPtr<Bitmap> Bitmap::to_bitmap_backed_by_shared_buffer() const
+{
+ if (m_shared_buffer)
+ return *this;
+ auto buffer = SharedBuffer::create_with_size(size_in_bytes());
+ if (!buffer)
+ return nullptr;
+ auto bitmap = Bitmap::create_with_shared_buffer(m_format, *buffer, m_size, palette_to_vector());
+ if (!bitmap)
+ return nullptr;
+ memcpy(buffer->data<void>(), scanline(0), size_in_bytes());
+ return bitmap;
+}
+
+Bitmap::~Bitmap()
+{
+ if (m_needs_munmap) {
+ int rc = munmap(m_data, size_in_bytes());
+ ASSERT(rc == 0);
+ }
+ m_data = nullptr;
+ delete[] m_palette;
+}
+
+void Bitmap::set_mmap_name([[maybe_unused]] const StringView& name)
+{
+ ASSERT(m_needs_munmap);
+#ifdef __serenity__
+ ::set_mmap_name(m_data, size_in_bytes(), name.to_string().characters());
+#endif
+}
+
+void Bitmap::fill(Color color)
+{
+ ASSERT(!is_indexed(m_format));
+ for (int y = 0; y < height(); ++y) {
+ auto* scanline = this->scanline(y);
+ fast_u32_fill(scanline, color.value(), width());
+ }
+}
+
+void Bitmap::set_volatile()
+{
+ ASSERT(m_purgeable);
+ if (m_volatile)
+ return;
+#ifdef __serenity__
+ int rc = madvise(m_data, size_in_bytes(), MADV_SET_VOLATILE);
+ if (rc < 0) {
+ perror("madvise(MADV_SET_VOLATILE)");
+ ASSERT_NOT_REACHED();
+ }
+#endif
+ m_volatile = true;
+}
+
+[[nodiscard]] bool Bitmap::set_nonvolatile()
+{
+ ASSERT(m_purgeable);
+ if (!m_volatile)
+ return true;
+#ifdef __serenity__
+ int rc = madvise(m_data, size_in_bytes(), MADV_SET_NONVOLATILE);
+ if (rc < 0) {
+ perror("madvise(MADV_SET_NONVOLATILE)");
+ ASSERT_NOT_REACHED();
+ }
+#else
+ int rc = 0;
+#endif
+ m_volatile = false;
+ return rc == 0;
+}
+
+int Bitmap::shbuf_id() const
+{
+ return m_shared_buffer ? m_shared_buffer->shbuf_id() : -1;
+}
+
+ShareableBitmap Bitmap::to_shareable_bitmap(pid_t peer_pid) const
+{
+ auto bitmap = to_bitmap_backed_by_shared_buffer();
+ if (!bitmap)
+ return {};
+ if (peer_pid > 0)
+ bitmap->shared_buffer()->share_with(peer_pid);
+ return ShareableBitmap(*bitmap);
+}
+
+Optional<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, const IntSize& size, [[maybe_unused]] Purgeable purgeable)
+{
+ if (size_would_overflow(format, size))
+ return {};
+
+ const auto pitch = minimum_pitch(size.width(), format);
+ const auto data_size_in_bytes = size_in_bytes(pitch, size.height());
+
+ int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
+ if (purgeable == Purgeable::Yes)
+ map_flags |= MAP_NORESERVE;
+#ifdef __serenity__
+ void* data = mmap_with_name(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0, String::format("GraphicsBitmap [%dx%d]", size.width(), size.height()).characters());
+#else
+ void* data = mmap(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0);
+#endif
+ if (data == MAP_FAILED) {
+ perror("mmap");
+ return {};
+ }
+ return { { data, pitch, data_size_in_bytes } };
+}
+
+void Bitmap::allocate_palette_from_format(BitmapFormat format, const Vector<RGBA32>& source_palette)
+{
+ size_t size = palette_size(format);
+ if (size == 0)
+ return;
+ m_palette = new RGBA32[size];
+ if (!source_palette.is_empty()) {
+ ASSERT(source_palette.size() == size);
+ memcpy(m_palette, source_palette.data(), size * sizeof(RGBA32));
+ }
+}
+
+Vector<RGBA32> Bitmap::palette_to_vector() const
+{
+ Vector<RGBA32> vector;
+ auto size = palette_size(m_format);
+ vector.ensure_capacity(size);
+ for (size_t i = 0; i < size; ++i)
+ vector.unchecked_append(palette_color(i).value());
+ return vector;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Bitmap.h b/Userland/Libraries/LibGfx/Bitmap.h
new file mode 100644
index 0000000000..4d0b236f57
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Bitmap.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Rect.h>
+
+#define ENUMERATE_IMAGE_FORMATS \
+ __ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \
+ __ENUMERATE_IMAGE_FORMAT(pgm, ".pgm") \
+ __ENUMERATE_IMAGE_FORMAT(png, ".png") \
+ __ENUMERATE_IMAGE_FORMAT(ppm, ".ppm") \
+ __ENUMERATE_IMAGE_FORMAT(gif, ".gif") \
+ __ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") \
+ __ENUMERATE_IMAGE_FORMAT(ico, ".ico") \
+ __ENUMERATE_IMAGE_FORMAT(jpg, ".jpg") \
+ __ENUMERATE_IMAGE_FORMAT(jpg, ".jpeg")
+
+namespace Gfx {
+
+enum class BitmapFormat {
+ Invalid,
+ Indexed1,
+ Indexed2,
+ Indexed4,
+ Indexed8,
+ RGB32,
+ RGBA32,
+};
+
+enum class StorageFormat {
+ Indexed8,
+ RGB32,
+ RGBA32,
+};
+
+static StorageFormat determine_storage_format(BitmapFormat format)
+{
+ switch (format) {
+ case BitmapFormat::RGB32:
+ return StorageFormat::RGB32;
+ case BitmapFormat::RGBA32:
+ return StorageFormat::RGBA32;
+ case BitmapFormat::Indexed1:
+ case BitmapFormat::Indexed2:
+ case BitmapFormat::Indexed4:
+ case BitmapFormat::Indexed8:
+ return StorageFormat::Indexed8;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+struct BackingStore;
+
+enum RotationDirection {
+ Left,
+ Right
+};
+
+class Bitmap : public RefCounted<Bitmap> {
+public:
+ static RefPtr<Bitmap> create(BitmapFormat, const IntSize&);
+ static RefPtr<Bitmap> create_shareable(BitmapFormat, const IntSize&);
+ static RefPtr<Bitmap> create_purgeable(BitmapFormat, const IntSize&);
+ static RefPtr<Bitmap> create_wrapper(BitmapFormat, const IntSize&, size_t pitch, void*);
+ static RefPtr<Bitmap> load_from_file(const StringView& path);
+ static RefPtr<Bitmap> create_with_shared_buffer(BitmapFormat, NonnullRefPtr<SharedBuffer>&&, const IntSize&);
+ static RefPtr<Bitmap> create_with_shared_buffer(BitmapFormat, NonnullRefPtr<SharedBuffer>&&, const IntSize&, const Vector<RGBA32>& palette);
+ static RefPtr<Bitmap> create_from_serialized_byte_buffer(ByteBuffer&& buffer);
+ static bool is_path_a_supported_image_format(const StringView& path)
+ {
+#define __ENUMERATE_IMAGE_FORMAT(Name, Ext) \
+ if (path.ends_with(Ext, CaseSensitivity::CaseInsensitive)) \
+ return true;
+ ENUMERATE_IMAGE_FORMATS
+#undef __ENUMERATE_IMAGE_FORMAT
+
+ return false;
+ }
+
+ RefPtr<Gfx::Bitmap> clone() const;
+
+ RefPtr<Gfx::Bitmap> rotated(Gfx::RotationDirection) const;
+ RefPtr<Gfx::Bitmap> flipped(Gfx::Orientation) const;
+ RefPtr<Bitmap> to_bitmap_backed_by_shared_buffer() const;
+ ByteBuffer serialize_to_byte_buffer() const;
+
+ ShareableBitmap to_shareable_bitmap(pid_t peer_pid = -1) const;
+
+ ~Bitmap();
+
+ u8* scanline_u8(int y);
+ const u8* scanline_u8(int y) const;
+ RGBA32* scanline(int y);
+ const RGBA32* scanline(int y) const;
+
+ IntRect rect() const { return { {}, m_size }; }
+ IntSize size() const { return m_size; }
+ int width() const { return m_size.width(); }
+ int height() const { return m_size.height(); }
+ size_t pitch() const { return m_pitch; }
+ int shbuf_id() const;
+
+ SharedBuffer* shared_buffer() { return m_shared_buffer.ptr(); }
+ const SharedBuffer* shared_buffer() const { return m_shared_buffer.ptr(); }
+
+ ALWAYS_INLINE bool is_indexed() const
+ {
+ return is_indexed(m_format);
+ }
+
+ ALWAYS_INLINE static bool is_indexed(BitmapFormat format)
+ {
+ return format == BitmapFormat::Indexed8 || format == BitmapFormat::Indexed4
+ || format == BitmapFormat::Indexed2 || format == BitmapFormat::Indexed1;
+ }
+
+ static size_t palette_size(BitmapFormat format)
+ {
+ switch (format) {
+ case BitmapFormat::Indexed1:
+ return 2;
+ case BitmapFormat::Indexed2:
+ return 4;
+ case BitmapFormat::Indexed4:
+ return 16;
+ case BitmapFormat::Indexed8:
+ return 256;
+ default:
+ return 0;
+ }
+ }
+
+ Vector<RGBA32> palette_to_vector() const;
+
+ static unsigned bpp_for_format(BitmapFormat format)
+ {
+ switch (format) {
+ case BitmapFormat::Indexed1:
+ return 1;
+ case BitmapFormat::Indexed2:
+ return 2;
+ case BitmapFormat::Indexed4:
+ return 4;
+ case BitmapFormat::Indexed8:
+ return 8;
+ case BitmapFormat::RGB32:
+ case BitmapFormat::RGBA32:
+ return 32;
+ default:
+ ASSERT_NOT_REACHED();
+ case BitmapFormat::Invalid:
+ return 0;
+ }
+ }
+
+ static size_t minimum_pitch(size_t width, BitmapFormat);
+
+ unsigned bpp() const
+ {
+ return bpp_for_format(m_format);
+ }
+
+ void fill(Color);
+
+ bool has_alpha_channel() const { return m_format == BitmapFormat::RGBA32; }
+ BitmapFormat format() const { return m_format; }
+
+ void set_mmap_name(const StringView&);
+
+ static constexpr size_t size_in_bytes(size_t pitch, int height) { return pitch * height; }
+ size_t size_in_bytes() const { return size_in_bytes(m_pitch, height()); }
+
+ Color palette_color(u8 index) const { return Color::from_rgba(m_palette[index]); }
+ void set_palette_color(u8 index, Color color) { m_palette[index] = color.value(); }
+
+ template<StorageFormat>
+ Color get_pixel(int x, int y) const;
+ Color get_pixel(int x, int y) const;
+ Color get_pixel(const IntPoint& position) const
+ {
+ return get_pixel(position.x(), position.y());
+ }
+
+ template<StorageFormat>
+ void set_pixel(int x, int y, Color);
+ void set_pixel(int x, int y, Color);
+ void set_pixel(const IntPoint& position, Color color)
+ {
+ set_pixel(position.x(), position.y(), color);
+ }
+
+ bool is_purgeable() const { return m_purgeable; }
+ bool is_volatile() const { return m_volatile; }
+ void set_volatile();
+ [[nodiscard]] bool set_nonvolatile();
+
+private:
+ enum class Purgeable {
+ No,
+ Yes
+ };
+ Bitmap(BitmapFormat, const IntSize&, Purgeable, const BackingStore&);
+ Bitmap(BitmapFormat, const IntSize&, size_t pitch, void*);
+ Bitmap(BitmapFormat, NonnullRefPtr<SharedBuffer>&&, const IntSize&, const Vector<RGBA32>& palette);
+
+ static Optional<BackingStore> allocate_backing_store(BitmapFormat, const IntSize&, Purgeable);
+
+ void allocate_palette_from_format(BitmapFormat, const Vector<RGBA32>& source_palette);
+
+ IntSize m_size;
+ void* m_data { nullptr };
+ RGBA32* m_palette { nullptr };
+ size_t m_pitch { 0 };
+ BitmapFormat m_format { BitmapFormat::Invalid };
+ bool m_needs_munmap { false };
+ bool m_purgeable { false };
+ bool m_volatile { false };
+ RefPtr<SharedBuffer> m_shared_buffer;
+};
+
+inline u8* Bitmap::scanline_u8(int y)
+{
+ ASSERT(y >= 0 && y < height());
+ return reinterpret_cast<u8*>(m_data) + (y * m_pitch);
+}
+
+inline const u8* Bitmap::scanline_u8(int y) const
+{
+ ASSERT(y >= 0 && y < height());
+ return reinterpret_cast<const u8*>(m_data) + (y * m_pitch);
+}
+
+inline RGBA32* Bitmap::scanline(int y)
+{
+ return reinterpret_cast<RGBA32*>(scanline_u8(y));
+}
+
+inline const RGBA32* Bitmap::scanline(int y) const
+{
+ return reinterpret_cast<const RGBA32*>(scanline_u8(y));
+}
+
+template<>
+inline Color Bitmap::get_pixel<StorageFormat::RGB32>(int x, int y) const
+{
+ ASSERT(x >= 0 && x < width());
+ return Color::from_rgb(scanline(y)[x]);
+}
+
+template<>
+inline Color Bitmap::get_pixel<StorageFormat::RGBA32>(int x, int y) const
+{
+ ASSERT(x >= 0 && x < width());
+ return Color::from_rgba(scanline(y)[x]);
+}
+
+template<>
+inline Color Bitmap::get_pixel<StorageFormat::Indexed8>(int x, int y) const
+{
+ ASSERT(x >= 0 && x < width());
+ return Color::from_rgb(m_palette[scanline_u8(y)[x]]);
+}
+
+inline Color Bitmap::get_pixel(int x, int y) const
+{
+ switch (determine_storage_format(m_format)) {
+ case StorageFormat::RGB32:
+ return get_pixel<StorageFormat::RGB32>(x, y);
+ case StorageFormat::RGBA32:
+ return get_pixel<StorageFormat::RGBA32>(x, y);
+ case StorageFormat::Indexed8:
+ return get_pixel<StorageFormat::Indexed8>(x, y);
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+template<>
+inline void Bitmap::set_pixel<StorageFormat::RGB32>(int x, int y, Color color)
+{
+ ASSERT(x >= 0 && x < width());
+ scanline(y)[x] = color.value();
+}
+template<>
+inline void Bitmap::set_pixel<StorageFormat::RGBA32>(int x, int y, Color color)
+{
+ ASSERT(x >= 0 && x < width());
+ scanline(y)[x] = color.value(); // drop alpha
+}
+inline void Bitmap::set_pixel(int x, int y, Color color)
+{
+ switch (determine_storage_format(m_format)) {
+ case StorageFormat::RGB32:
+ set_pixel<StorageFormat::RGB32>(x, y, color);
+ break;
+ case StorageFormat::RGBA32:
+ set_pixel<StorageFormat::RGBA32>(x, y, color);
+ break;
+ case StorageFormat::Indexed8:
+ ASSERT_NOT_REACHED();
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BitmapFont.cpp b/Userland/Libraries/LibGfx/BitmapFont.cpp
new file mode 100644
index 0000000000..e5e24cbde5
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BitmapFont.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "BitmapFont.h"
+#include "Bitmap.h"
+#include "Emoji.h"
+#include <AK/StdLibExtras.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <AK/Vector.h>
+#include <AK/kmalloc.h>
+#include <LibCore/FileStream.h>
+#include <LibGfx/FontDatabase.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+namespace Gfx {
+
+struct [[gnu::packed]] FontFileHeader {
+ char magic[4];
+ u8 glyph_width;
+ u8 glyph_height;
+ u8 type;
+ u8 is_variable_width;
+ u8 glyph_spacing;
+ u8 baseline;
+ u8 mean_line;
+ u8 presentation_size;
+ u16 weight;
+ char name[32];
+ char family[32];
+};
+
+NonnullRefPtr<Font> BitmapFont::clone() const
+{
+ size_t bytes_per_glyph = sizeof(u32) * glyph_height();
+ auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * m_glyph_count));
+ memcpy(new_rows, m_rows, bytes_per_glyph * m_glyph_count);
+ auto* new_widths = static_cast<u8*>(malloc(m_glyph_count));
+ if (m_glyph_widths)
+ memcpy(new_widths, m_glyph_widths, m_glyph_count);
+ else
+ memset(new_widths, m_glyph_width, m_glyph_count);
+ return adopt(*new BitmapFont(m_name, m_family, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing, m_type, m_baseline, m_mean_line, m_presentation_size, m_weight, true));
+}
+
+NonnullRefPtr<BitmapFont> BitmapFont::create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type)
+{
+ size_t bytes_per_glyph = sizeof(u32) * glyph_height;
+ size_t count = glyph_count_by_type(type);
+ auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * count));
+ memset(new_rows, 0, bytes_per_glyph * count);
+ auto* new_widths = static_cast<u8*>(malloc(count));
+ memset(new_widths, glyph_width, count);
+ return adopt(*new BitmapFont("Untitled", "Untitled", new_rows, new_widths, fixed, glyph_width, glyph_height, 1, type, 0, 0, 0, 400, true));
+}
+
+BitmapFont::BitmapFont(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays)
+ : m_name(name)
+ , m_family(family)
+ , m_type(type)
+ , m_rows(rows)
+ , m_glyph_widths(widths)
+ , m_glyph_width(glyph_width)
+ , m_glyph_height(glyph_height)
+ , m_min_glyph_width(glyph_width)
+ , m_max_glyph_width(glyph_width)
+ , m_glyph_spacing(glyph_spacing)
+ , m_baseline(baseline)
+ , m_mean_line(mean_line)
+ , m_presentation_size(presentation_size)
+ , m_weight(weight)
+ , m_fixed_width(is_fixed_width)
+ , m_owns_arrays(owns_arrays)
+{
+ update_x_height();
+
+ m_glyph_count = glyph_count_by_type(m_type);
+
+ if (!m_fixed_width) {
+ u8 maximum = 0;
+ u8 minimum = 255;
+ for (size_t i = 0; i < m_glyph_count; ++i) {
+ minimum = min(minimum, m_glyph_widths[i]);
+ maximum = max(maximum, m_glyph_widths[i]);
+ }
+ m_min_glyph_width = minimum;
+ m_max_glyph_width = maximum;
+ }
+}
+
+BitmapFont::~BitmapFont()
+{
+ if (m_owns_arrays) {
+ free(m_glyph_widths);
+ free(m_rows);
+ }
+}
+
+RefPtr<BitmapFont> BitmapFont::load_from_memory(const u8* data)
+{
+ auto& header = *reinterpret_cast<const FontFileHeader*>(data);
+ if (memcmp(header.magic, "!Fnt", 4)) {
+ dbgln("header.magic != '!Fnt', instead it's '{:c}{:c}{:c}{:c}'", header.magic[0], header.magic[1], header.magic[2], header.magic[3]);
+ return nullptr;
+ }
+ if (header.name[sizeof(header.name) - 1] != '\0') {
+ dbgln("Font name not fully null-terminated");
+ return nullptr;
+ }
+
+ if (header.family[sizeof(header.family) - 1] != '\0') {
+ dbgln("Font family not fully null-terminated");
+ return nullptr;
+ }
+
+ FontTypes type;
+ if (header.type == 0)
+ type = FontTypes::Default;
+ else if (header.type == 1)
+ type = FontTypes::LatinExtendedA;
+ else
+ ASSERT_NOT_REACHED();
+
+ size_t count = glyph_count_by_type(type);
+ size_t bytes_per_glyph = sizeof(unsigned) * header.glyph_height;
+
+ auto* rows = const_cast<unsigned*>((const unsigned*)(data + sizeof(FontFileHeader)));
+ u8* widths = nullptr;
+ if (header.is_variable_width)
+ widths = (u8*)(rows) + count * bytes_per_glyph;
+ return adopt(*new BitmapFont(String(header.name), String(header.family), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing, type, header.baseline, header.mean_line, header.presentation_size, header.weight));
+}
+
+size_t BitmapFont::glyph_count_by_type(FontTypes type)
+{
+ if (type == FontTypes::Default)
+ return 256;
+
+ if (type == FontTypes::LatinExtendedA)
+ return 384;
+
+ dbgln("Unknown font type: {}", (int)type);
+ ASSERT_NOT_REACHED();
+}
+
+RefPtr<BitmapFont> BitmapFont::load_from_file(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+
+ auto font = load_from_memory((const u8*)file_or_error.value()->data());
+ if (!font)
+ return nullptr;
+
+ font->m_mapped_file = file_or_error.release_value();
+ return font;
+}
+
+bool BitmapFont::write_to_file(const StringView& path)
+{
+ FontFileHeader header;
+ memset(&header, 0, sizeof(FontFileHeader));
+ memcpy(header.magic, "!Fnt", 4);
+ header.glyph_width = m_glyph_width;
+ header.glyph_height = m_glyph_height;
+ header.type = m_type;
+ header.baseline = m_baseline;
+ header.mean_line = m_mean_line;
+ header.is_variable_width = !m_fixed_width;
+ header.glyph_spacing = m_glyph_spacing;
+ header.presentation_size = m_presentation_size;
+ header.weight = m_weight;
+ memcpy(header.name, m_name.characters(), min(m_name.length(), sizeof(header.name) - 1));
+ memcpy(header.family, m_family.characters(), min(m_family.length(), sizeof(header.family) - 1));
+
+ size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
+ size_t count = glyph_count_by_type(m_type);
+
+ auto stream_result = Core::OutputFileStream::open_buffered(path);
+ if (stream_result.is_error())
+ return false;
+ auto& stream = stream_result.value();
+
+ stream << ReadonlyBytes { &header, sizeof(header) };
+ stream << ReadonlyBytes { m_rows, count * bytes_per_glyph };
+ stream << ReadonlyBytes { m_glyph_widths, count };
+
+ stream.flush();
+ if (stream.handle_any_error())
+ return false;
+
+ return true;
+}
+
+GlyphBitmap BitmapFont::glyph_bitmap(u32 code_point) const
+{
+ return GlyphBitmap(&m_rows[code_point * m_glyph_height], { glyph_width(code_point), m_glyph_height });
+}
+
+int BitmapFont::glyph_or_emoji_width(u32 code_point) const
+{
+ if (code_point < m_glyph_count)
+ return glyph_width(code_point);
+
+ if (m_fixed_width)
+ return m_glyph_width;
+
+ auto* emoji = Emoji::emoji_for_code_point(code_point);
+ if (emoji == nullptr)
+ return glyph_width('?');
+ return emoji->size().width();
+}
+
+int BitmapFont::width(const StringView& string) const
+{
+ Utf8View utf8 { string };
+ return width(utf8);
+}
+
+int BitmapFont::width(const Utf8View& utf8) const
+{
+ bool first = true;
+ int width = 0;
+
+ for (u32 code_point : utf8) {
+ if (!first)
+ width += glyph_spacing();
+ first = false;
+ width += glyph_or_emoji_width(code_point);
+ }
+
+ return width;
+}
+
+int BitmapFont::width(const Utf32View& view) const
+{
+ if (view.length() == 0)
+ return 0;
+ int width = (view.length() - 1) * glyph_spacing();
+ for (size_t i = 0; i < view.length(); ++i)
+ width += glyph_or_emoji_width(view.code_points()[i]);
+ return width;
+}
+
+void BitmapFont::set_type(FontTypes type)
+{
+ if (type == m_type)
+ return;
+
+ if (type == FontTypes::Default)
+ return;
+
+ size_t new_glyph_count = glyph_count_by_type(type);
+ if (new_glyph_count <= m_glyph_count) {
+ m_glyph_count = new_glyph_count;
+ return;
+ }
+
+ int item_count_to_copy = min(m_glyph_count, new_glyph_count);
+
+ size_t bytes_per_glyph = sizeof(u32) * glyph_height();
+
+ auto* new_rows = static_cast<unsigned*>(kmalloc(bytes_per_glyph * new_glyph_count));
+ memset(new_rows, (unsigned)0, bytes_per_glyph * new_glyph_count);
+ memcpy(new_rows, m_rows, bytes_per_glyph * item_count_to_copy);
+
+ auto* new_widths = static_cast<u8*>(kmalloc(new_glyph_count));
+ memset(new_widths, (u8)0, new_glyph_count);
+ memcpy(new_widths, m_glyph_widths, item_count_to_copy);
+
+ kfree(m_rows);
+ kfree(m_glyph_widths);
+
+ m_type = type;
+ m_glyph_count = new_glyph_count;
+ m_rows = new_rows;
+ m_glyph_widths = new_widths;
+}
+
+String BitmapFont::qualified_name() const
+{
+ return String::formatted("{} {} {}", family(), presentation_size(), weight());
+}
+
+const Font& BitmapFont::bold_variant() const
+{
+ if (m_bold_variant)
+ return *m_bold_variant;
+ m_bold_variant = Gfx::FontDatabase::the().get(m_family, m_presentation_size, 700);
+ if (!m_bold_variant)
+ m_bold_variant = this;
+ return *m_bold_variant;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/BitmapFont.h b/Userland/Libraries/LibGfx/BitmapFont.h
new file mode 100644
index 0000000000..c3ca9f4a1d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/BitmapFont.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/MappedFile.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+enum FontTypes {
+ Default = 0,
+ LatinExtendedA = 1
+};
+
+class BitmapFont : public Font {
+public:
+ NonnullRefPtr<Font> clone() const;
+ static NonnullRefPtr<BitmapFont> create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type);
+
+ static RefPtr<BitmapFont> load_from_file(const StringView& path);
+ bool write_to_file(const StringView& path);
+
+ ~BitmapFont();
+
+ u8 presentation_size() const { return m_presentation_size; }
+ void set_presentation_size(u8 size) { m_presentation_size = size; }
+
+ u16 weight() const { return m_weight; }
+ void set_weight(u16 weight) { m_weight = weight; }
+
+ GlyphBitmap glyph_bitmap(u32 code_point) const;
+
+ u8 glyph_width(size_t ch) const { return m_fixed_width ? m_glyph_width : m_glyph_widths[ch]; }
+ int glyph_or_emoji_width(u32 code_point) const;
+ u8 glyph_height() const { return m_glyph_height; }
+ int x_height() const { return m_x_height; }
+
+ u8 min_glyph_width() const { return m_min_glyph_width; }
+ u8 max_glyph_width() const { return m_max_glyph_width; }
+ u8 glyph_fixed_width() const { return m_glyph_width; }
+
+ u8 baseline() const { return m_baseline; }
+ void set_baseline(u8 baseline)
+ {
+ m_baseline = baseline;
+ update_x_height();
+ }
+
+ u8 mean_line() const { return m_mean_line; }
+ void set_mean_line(u8 mean_line)
+ {
+ m_mean_line = mean_line;
+ update_x_height();
+ }
+
+ int width(const StringView&) const;
+ int width(const Utf8View&) const;
+ int width(const Utf32View&) const;
+
+ const String& name() const { return m_name; }
+ void set_name(String name) { m_name = move(name); }
+
+ bool is_fixed_width() const { return m_fixed_width; }
+ void set_fixed_width(bool b) { m_fixed_width = b; }
+
+ u8 glyph_spacing() const { return m_glyph_spacing; }
+ void set_glyph_spacing(u8 spacing) { m_glyph_spacing = spacing; }
+
+ void set_glyph_width(size_t ch, u8 width)
+ {
+ ASSERT(m_glyph_widths);
+ m_glyph_widths[ch] = width;
+ }
+
+ int glyph_count() const { return m_glyph_count; }
+
+ FontTypes type() { return m_type; }
+ void set_type(FontTypes type);
+
+ const String& family() const { return m_family; }
+ void set_family(String family) { m_family = move(family); }
+
+ String qualified_name() const;
+
+ const Font& bold_variant() const;
+
+private:
+ BitmapFont(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays = false);
+
+ static RefPtr<BitmapFont> load_from_memory(const u8*);
+ static size_t glyph_count_by_type(FontTypes type);
+
+ void update_x_height() { m_x_height = m_baseline - m_mean_line; };
+
+ String m_name;
+ String m_family;
+ FontTypes m_type;
+ size_t m_glyph_count { 256 };
+
+ unsigned* m_rows { nullptr };
+ u8* m_glyph_widths { nullptr };
+ RefPtr<MappedFile> m_mapped_file;
+
+ u8 m_glyph_width { 0 };
+ u8 m_glyph_height { 0 };
+ u8 m_x_height { 0 };
+ u8 m_min_glyph_width { 0 };
+ u8 m_max_glyph_width { 0 };
+ u8 m_glyph_spacing { 0 };
+ u8 m_baseline { 0 };
+ u8 m_mean_line { 0 };
+ u8 m_presentation_size { 0 };
+ u16 m_weight { 0 };
+
+ bool m_fixed_width { false };
+ bool m_owns_arrays { false };
+
+ mutable RefPtr<Gfx::Font> m_bold_variant;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt
new file mode 100644
index 0000000000..2a6d9750db
--- /dev/null
+++ b/Userland/Libraries/LibGfx/CMakeLists.txt
@@ -0,0 +1,37 @@
+set(SOURCES
+ AffineTransform.cpp
+ Bitmap.cpp
+ BitmapFont.cpp
+ BMPLoader.cpp
+ BMPWriter.cpp
+ CharacterBitmap.cpp
+ ClassicStylePainter.cpp
+ ClassicWindowTheme.cpp
+ Color.cpp
+ DisjointRectSet.cpp
+ Emoji.cpp
+ Font.cpp
+ FontDatabase.cpp
+ GIFLoader.cpp
+ ICOLoader.cpp
+ ImageDecoder.cpp
+ JPGLoader.cpp
+ Painter.cpp
+ Palette.cpp
+ Path.cpp
+ PBMLoader.cpp
+ PGMLoader.cpp
+ PNGLoader.cpp
+ PPMLoader.cpp
+ Point.cpp
+ Rect.cpp
+ ShareableBitmap.cpp
+ Size.cpp
+ StylePainter.cpp
+ SystemTheme.cpp
+ Triangle.cpp
+ WindowTheme.cpp
+)
+
+serenity_lib(LibGfx gfx)
+target_link_libraries(LibGfx LibM LibCore)
diff --git a/Userland/Libraries/LibGfx/CharacterBitmap.cpp b/Userland/Libraries/LibGfx/CharacterBitmap.cpp
new file mode 100644
index 0000000000..d51ff1d080
--- /dev/null
+++ b/Userland/Libraries/LibGfx/CharacterBitmap.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CharacterBitmap.h"
+
+namespace Gfx {
+
+CharacterBitmap::CharacterBitmap(const char* ascii_data, unsigned width, unsigned height)
+ : m_bits(ascii_data)
+ , m_size(width, height)
+{
+}
+
+CharacterBitmap::~CharacterBitmap()
+{
+}
+
+NonnullRefPtr<CharacterBitmap> CharacterBitmap::create_from_ascii(const char* asciiData, unsigned width, unsigned height)
+{
+ return adopt(*new CharacterBitmap(asciiData, width, height));
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/CharacterBitmap.h b/Userland/Libraries/LibGfx/CharacterBitmap.h
new file mode 100644
index 0000000000..9780383570
--- /dev/null
+++ b/Userland/Libraries/LibGfx/CharacterBitmap.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Size.h"
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+
+namespace Gfx {
+
+class CharacterBitmap : public RefCounted<CharacterBitmap> {
+public:
+ static NonnullRefPtr<CharacterBitmap> create_from_ascii(const char* asciiData, unsigned width, unsigned height);
+ ~CharacterBitmap();
+
+ bool bit_at(unsigned x, unsigned y) const { return m_bits[y * width() + x] == '#'; }
+ const char* bits() const { return m_bits; }
+
+ IntSize size() const { return m_size; }
+ unsigned width() const { return m_size.width(); }
+ unsigned height() const { return m_size.height(); }
+
+private:
+ CharacterBitmap(const char* b, unsigned w, unsigned h);
+
+ const char* m_bits { nullptr };
+ IntSize m_size;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicStylePainter.cpp b/Userland/Libraries/LibGfx/ClassicStylePainter.cpp
new file mode 100644
index 0000000000..95c9e9ffb6
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicStylePainter.cpp
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020 Sarah Taube <metalflakecobaltpaint@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StringView.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/ClassicStylePainter.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Palette.h>
+
+namespace Gfx {
+
+void ClassicStylePainter::paint_tab_button(Painter& painter, const IntRect& rect, const Palette& palette, bool active, bool hovered, bool enabled, bool top)
+{
+ Color base_color = palette.button();
+ Color highlight_color2 = palette.threed_highlight();
+ Color shadow_color1 = palette.threed_shadow1();
+ Color shadow_color2 = palette.threed_shadow2();
+
+ if (hovered && enabled && !active)
+ base_color = palette.hover_highlight();
+
+ PainterStateSaver saver(painter);
+ painter.translate(rect.location());
+
+ if (top) {
+ // Base
+ painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 1 }, base_color);
+
+ // Top line
+ painter.draw_line({ 2, 0 }, { rect.width() - 3, 0 }, highlight_color2);
+
+ // Left side
+ painter.draw_line({ 0, 2 }, { 0, rect.height() - 1 }, highlight_color2);
+ painter.set_pixel({ 1, 1 }, highlight_color2);
+
+ // Right side
+
+ IntPoint top_right_outer { rect.width() - 1, 2 };
+ IntPoint bottom_right_outer { rect.width() - 1, rect.height() - 1 };
+ painter.draw_line(top_right_outer, bottom_right_outer, shadow_color2);
+
+ IntPoint top_right_inner { rect.width() - 2, 2 };
+ IntPoint bottom_right_inner { rect.width() - 2, rect.height() - 1 };
+ painter.draw_line(top_right_inner, bottom_right_inner, shadow_color1);
+
+ painter.set_pixel(rect.width() - 2, 1, shadow_color2);
+ } else {
+ // Base
+ painter.fill_rect({ 0, 0, rect.width() - 1, rect.height() }, base_color);
+
+ // Bottom line
+ painter.draw_line({ 2, rect.height() - 1 }, { rect.width() - 3, rect.height() - 1 }, shadow_color2);
+
+ // Left side
+ painter.draw_line({ 0, 0 }, { 0, rect.height() - 3 }, highlight_color2);
+ painter.set_pixel({ 1, rect.height() - 2 }, highlight_color2);
+
+ // Right side
+ IntPoint top_right_outer { rect.width() - 1, 0 };
+ IntPoint bottom_right_outer { rect.width() - 1, rect.height() - 3 };
+ painter.draw_line(top_right_outer, bottom_right_outer, shadow_color2);
+
+ IntPoint top_right_inner { rect.width() - 2, 0 };
+ IntPoint bottom_right_inner { rect.width() - 2, rect.height() - 3 };
+ painter.draw_line(top_right_inner, bottom_right_inner, shadow_color1);
+
+ painter.set_pixel(rect.width() - 2, rect.height() - 2, shadow_color2);
+ }
+}
+
+static void paint_button_new(Painter& painter, const IntRect& a_rect, const Palette& palette, bool pressed, bool checked, bool hovered, bool enabled, bool focused)
+{
+ Color button_color = palette.button();
+ Color highlight_color = palette.threed_highlight();
+ Color shadow_color1 = palette.threed_shadow1();
+ Color shadow_color2 = palette.threed_shadow2();
+
+ if (checked && enabled) {
+ if (hovered)
+ button_color = palette.hover_highlight();
+ else
+ button_color = palette.button();
+ } else if (hovered && enabled)
+ button_color = palette.hover_highlight();
+
+ PainterStateSaver saver(painter);
+
+ auto rect = a_rect;
+ if (focused) {
+ painter.draw_rect(a_rect, palette.threed_shadow2());
+ rect.shrink(2, 2);
+ }
+
+ painter.translate(rect.location());
+
+ if (pressed || checked) {
+ // Base
+ Gfx::IntRect base_rect { 1, 1, rect.width() - 2, rect.height() - 2 };
+
+ if (checked && !pressed)
+ painter.fill_rect_with_dither_pattern(base_rect, palette.button().lightened(1.3f), palette.button());
+ else
+ painter.fill_rect(base_rect, button_color);
+
+ // Top shadow
+ painter.draw_line({ 0, 0 }, { rect.width() - 2, 0 }, shadow_color2);
+ painter.draw_line({ 0, 0 }, { 0, rect.height() - 2 }, shadow_color2);
+
+ // Sunken shadow
+ painter.draw_line({ 1, 1 }, { rect.width() - 3, 1 }, shadow_color1);
+ painter.draw_line({ 1, 2 }, { 1, rect.height() - 3 }, shadow_color1);
+
+ // Outer highlight
+ painter.draw_line({ 0, rect.height() - 1 }, { rect.width() - 1, rect.height() - 1 }, highlight_color);
+ painter.draw_line({ rect.width() - 1, 0 }, { rect.width() - 1, rect.height() - 2 }, highlight_color);
+
+ // Inner highlight
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, palette.button());
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, palette.button());
+ } else {
+ // Base
+ painter.fill_rect({ 0, 0, rect.width(), rect.height() }, button_color);
+
+ // Top highlight
+ painter.draw_line({ 1, 1 }, { rect.width() - 3, 1 }, highlight_color);
+ painter.draw_line({ 1, 1 }, { 1, rect.height() - 3 }, highlight_color);
+
+ // Outer shadow
+ painter.draw_line({ 0, rect.height() - 1 }, { rect.width() - 1, rect.height() - 1 }, shadow_color2);
+ painter.draw_line({ rect.width() - 1, 0 }, { rect.width() - 1, rect.height() - 2 }, shadow_color2);
+
+ // Inner shadow
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color1);
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color1);
+ }
+}
+
+void ClassicStylePainter::paint_button(Painter& painter, const IntRect& rect, const Palette& palette, ButtonStyle button_style, bool pressed, bool hovered, bool checked, bool enabled, bool focused)
+{
+ if (button_style == ButtonStyle::Normal)
+ return paint_button_new(painter, rect, palette, pressed, checked, hovered, enabled, focused);
+
+ if (button_style == ButtonStyle::CoolBar && !enabled)
+ return;
+
+ Color button_color = palette.button();
+ Color highlight_color = palette.threed_highlight();
+ Color shadow_color = palette.threed_shadow1();
+
+ PainterStateSaver saver(painter);
+ painter.translate(rect.location());
+
+ if (pressed || checked) {
+ // Base
+ IntRect base_rect { 1, 1, rect.width() - 2, rect.height() - 2 };
+ if (checked && !pressed) {
+ painter.fill_rect_with_dither_pattern(base_rect, palette.button().lightened(1.3f), palette.button());
+ } else {
+ painter.fill_rect(base_rect, button_color);
+ }
+
+ // Sunken shadow
+ painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, shadow_color);
+ painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, shadow_color);
+
+ // Bottom highlight
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, highlight_color);
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, highlight_color);
+ } else if (button_style == ButtonStyle::CoolBar && hovered) {
+ // Base
+ painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
+
+ // Top highlight
+ painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, highlight_color);
+ painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, highlight_color);
+
+ // Bottom shadow
+ painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color);
+ painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color);
+ }
+}
+
+void ClassicStylePainter::paint_surface(Painter& painter, const IntRect& rect, const Palette& palette, bool paint_vertical_lines, bool paint_top_line)
+{
+ painter.fill_rect({ rect.x(), rect.y() + 1, rect.width(), rect.height() - 2 }, palette.button());
+ painter.draw_line(rect.top_left(), rect.top_right(), paint_top_line ? palette.threed_highlight() : palette.button());
+ painter.draw_line(rect.bottom_left(), rect.bottom_right(), palette.threed_shadow1());
+ if (paint_vertical_lines) {
+ painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), palette.threed_highlight());
+ painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), palette.threed_shadow1());
+ }
+}
+
+void ClassicStylePainter::paint_frame(Painter& painter, const IntRect& rect, const Palette& palette, FrameShape shape, FrameShadow shadow, int thickness, bool skip_vertical_lines)
+{
+ Color top_left_color;
+ Color bottom_right_color;
+ Color dark_shade = palette.threed_shadow1();
+ Color light_shade = palette.threed_highlight();
+
+ if (shape == FrameShape::Container && thickness >= 2) {
+ if (shadow == FrameShadow::Raised) {
+ dark_shade = palette.threed_shadow2();
+ }
+ }
+
+ if (shadow == FrameShadow::Raised) {
+ top_left_color = light_shade;
+ bottom_right_color = dark_shade;
+ } else if (shadow == FrameShadow::Sunken) {
+ top_left_color = dark_shade;
+ bottom_right_color = light_shade;
+ } else if (shadow == FrameShadow::Plain) {
+ top_left_color = dark_shade;
+ bottom_right_color = dark_shade;
+ }
+
+ if (thickness >= 1) {
+ painter.draw_line(rect.top_left(), rect.top_right(), top_left_color);
+ painter.draw_line(rect.bottom_left(), rect.bottom_right(), bottom_right_color);
+
+ if (shape != FrameShape::Panel || !skip_vertical_lines) {
+ painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), top_left_color);
+ painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), bottom_right_color);
+ }
+ }
+
+ if (shape == FrameShape::Container && thickness >= 2) {
+ Color top_left_color;
+ Color bottom_right_color;
+ Color dark_shade = palette.threed_shadow2();
+ Color light_shade = palette.button();
+ if (shadow == FrameShadow::Raised) {
+ dark_shade = palette.threed_shadow1();
+ top_left_color = light_shade;
+ bottom_right_color = dark_shade;
+ } else if (shadow == FrameShadow::Sunken) {
+ top_left_color = dark_shade;
+ bottom_right_color = light_shade;
+ } else if (shadow == FrameShadow::Plain) {
+ top_left_color = dark_shade;
+ bottom_right_color = dark_shade;
+ }
+ IntRect inner_container_frame_rect = rect.shrunken(2, 2);
+ painter.draw_line(inner_container_frame_rect.top_left(), inner_container_frame_rect.top_right(), top_left_color);
+ painter.draw_line(inner_container_frame_rect.bottom_left(), inner_container_frame_rect.bottom_right(), bottom_right_color);
+ painter.draw_line(inner_container_frame_rect.top_left().translated(0, 1), inner_container_frame_rect.bottom_left().translated(0, -1), top_left_color);
+ painter.draw_line(inner_container_frame_rect.top_right(), inner_container_frame_rect.bottom_right().translated(0, -1), bottom_right_color);
+ }
+
+ if (shape == FrameShape::Box && thickness >= 2) {
+ swap(top_left_color, bottom_right_color);
+ IntRect inner_rect = rect.shrunken(2, 2);
+ painter.draw_line(inner_rect.top_left(), inner_rect.top_right(), top_left_color);
+ painter.draw_line(inner_rect.bottom_left(), inner_rect.bottom_right(), bottom_right_color);
+ painter.draw_line(inner_rect.top_left().translated(0, 1), inner_rect.bottom_left().translated(0, -1), top_left_color);
+ painter.draw_line(inner_rect.top_right(), inner_rect.bottom_right().translated(0, -1), bottom_right_color);
+ }
+}
+
+void ClassicStylePainter::paint_window_frame(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ Color base_color = palette.button();
+ Color dark_shade = palette.threed_shadow2();
+ Color mid_shade = palette.threed_shadow1();
+ Color light_shade = palette.threed_highlight();
+
+ painter.draw_line(rect.top_left(), rect.top_right(), base_color);
+ painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left(), base_color);
+ painter.draw_line(rect.top_left().translated(1, 1), rect.top_right().translated(-1, 1), light_shade);
+ painter.draw_line(rect.top_left().translated(1, 1), rect.bottom_left().translated(1, -1), light_shade);
+ painter.draw_line(rect.top_left().translated(2, 2), rect.top_right().translated(-2, 2), base_color);
+ painter.draw_line(rect.top_left().translated(2, 2), rect.bottom_left().translated(2, -2), base_color);
+ painter.draw_line(rect.top_left().translated(3, 3), rect.top_right().translated(-3, 3), base_color);
+ painter.draw_line(rect.top_left().translated(3, 3), rect.bottom_left().translated(3, -3), base_color);
+
+ painter.draw_line(rect.top_right(), rect.bottom_right(), dark_shade);
+ painter.draw_line(rect.top_right().translated(-1, 1), rect.bottom_right().translated(-1, -1), mid_shade);
+ painter.draw_line(rect.top_right().translated(-2, 2), rect.bottom_right().translated(-2, -2), base_color);
+ painter.draw_line(rect.top_right().translated(-3, 3), rect.bottom_right().translated(-3, -3), base_color);
+ painter.draw_line(rect.bottom_left(), rect.bottom_right(), dark_shade);
+ painter.draw_line(rect.bottom_left().translated(1, -1), rect.bottom_right().translated(-1, -1), mid_shade);
+ painter.draw_line(rect.bottom_left().translated(2, -2), rect.bottom_right().translated(-2, -2), base_color);
+ painter.draw_line(rect.bottom_left().translated(3, -3), rect.bottom_right().translated(-3, -3), base_color);
+}
+
+void ClassicStylePainter::paint_progress_bar(Painter& painter, const IntRect& rect, const Palette& palette, int min, int max, int value, const StringView& text)
+{
+ // First we fill the entire widget with the gradient. This incurs a bit of
+ // overdraw but ensures a consistent look throughout the progression.
+ Color start_color = palette.active_window_border1();
+ Color end_color = palette.active_window_border2();
+ painter.fill_rect_with_gradient(rect, start_color, end_color);
+
+ if (!text.is_null()) {
+ painter.draw_text(rect.translated(1, 1), text, TextAlignment::Center, palette.base_text());
+ painter.draw_text(rect, text, TextAlignment::Center, palette.base_text().inverted());
+ }
+
+ float range_size = max - min;
+ float progress = (value - min) / range_size;
+
+ // Then we carve out a hole in the remaining part of the widget.
+ // We draw the text a third time, clipped and inverse, for sharp contrast.
+ float progress_width = progress * rect.width();
+ IntRect hole_rect { (int)progress_width, 0, (int)(rect.width() - progress_width), rect.height() };
+ hole_rect.move_by(rect.location());
+ hole_rect.set_right_without_resize(rect.right());
+ PainterStateSaver saver(painter);
+ painter.fill_rect(hole_rect, palette.base());
+
+ painter.add_clip_rect(hole_rect);
+ if (!text.is_null())
+ painter.draw_text(rect.translated(0, 0), text, TextAlignment::Center, palette.base_text());
+}
+
+static RefPtr<Gfx::Bitmap> s_unfilled_circle_bitmap;
+static RefPtr<Gfx::Bitmap> s_filled_circle_bitmap;
+static RefPtr<Gfx::Bitmap> s_changing_filled_circle_bitmap;
+static RefPtr<Gfx::Bitmap> s_changing_unfilled_circle_bitmap;
+
+static const Gfx::Bitmap& circle_bitmap(bool checked, bool changing)
+{
+ if (changing)
+ return checked ? *s_changing_filled_circle_bitmap : *s_changing_unfilled_circle_bitmap;
+ return checked ? *s_filled_circle_bitmap : *s_unfilled_circle_bitmap;
+}
+
+void ClassicStylePainter::paint_radio_button(Painter& painter, const IntRect& rect, const Palette&, bool is_checked, bool is_being_pressed)
+{
+ if (!s_unfilled_circle_bitmap) {
+ s_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/unfilled-radio-circle.png");
+ s_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/filled-radio-circle.png");
+ s_changing_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/changing-filled-radio-circle.png");
+ s_changing_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/changing-unfilled-radio-circle.png");
+ }
+
+ auto& bitmap = circle_bitmap(is_checked, is_being_pressed);
+ painter.blit(rect.location(), bitmap, bitmap.rect());
+}
+
+static const char* s_checked_bitmap_data = {
+ " "
+ " # "
+ " ## "
+ " ### "
+ " ## ### "
+ " ##### "
+ " ### "
+ " # "
+ " "
+};
+
+static Gfx::CharacterBitmap* s_checked_bitmap;
+static const int s_checked_bitmap_width = 9;
+static const int s_checked_bitmap_height = 9;
+
+void ClassicStylePainter::paint_check_box(Painter& painter, const IntRect& rect, const Palette& palette, bool is_enabled, bool is_checked, bool is_being_pressed)
+{
+ painter.fill_rect(rect, is_enabled ? palette.base() : palette.window());
+ paint_frame(painter, rect, palette, Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
+
+ if (is_being_pressed) {
+ // FIXME: This color should not be hard-coded.
+ painter.draw_rect(rect.shrunken(4, 4), Color::MidGray);
+ }
+
+ if (is_checked) {
+ if (!s_checked_bitmap)
+ s_checked_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_checked_bitmap_data, s_checked_bitmap_width, s_checked_bitmap_height).leak_ref();
+ painter.draw_bitmap(rect.shrunken(4, 4).location(), *s_checked_bitmap, is_enabled ? palette.base_text() : palette.threed_shadow1());
+ }
+}
+
+void ClassicStylePainter::paint_transparency_grid(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ painter.fill_rect_with_checkerboard(rect, { 8, 8 }, palette.base().darkened(0.9), palette.base());
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicStylePainter.h b/Userland/Libraries/LibGfx/ClassicStylePainter.h
new file mode 100644
index 0000000000..de5302ecb5
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicStylePainter.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020 Sarah Taube <metalflakecobaltpaint@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/StylePainter.h>
+
+namespace Gfx {
+
+class ClassicStylePainter : public BaseStylePainter {
+public:
+ void paint_button(Painter&, const IntRect&, const Palette&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true, bool focused = false) override;
+ void paint_tab_button(Painter&, const IntRect&, const Palette&, bool active, bool hovered, bool enabled, bool top) override;
+ void paint_surface(Painter&, const IntRect&, const Palette&, bool paint_vertical_lines = true, bool paint_top_line = true) override;
+ void paint_frame(Painter&, const IntRect&, const Palette&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false) override;
+ void paint_window_frame(Painter&, const IntRect&, const Palette&) override;
+ void paint_progress_bar(Painter&, const IntRect&, const Palette&, int min, int max, int value, const StringView& text) override;
+ void paint_radio_button(Painter&, const IntRect&, const Palette&, bool is_checked, bool is_being_pressed) override;
+ void paint_check_box(Painter&, const IntRect&, const Palette&, bool is_enabled, bool is_checked, bool is_being_pressed) override;
+ void paint_transparency_grid(Painter&, const IntRect&, const Palette&) override;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicWindowTheme.cpp b/Userland/Libraries/LibGfx/ClassicWindowTheme.cpp
new file mode 100644
index 0000000000..5476bec441
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicWindowTheme.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ClassicWindowTheme.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+namespace Gfx {
+
+ClassicWindowTheme::ClassicWindowTheme()
+{
+}
+
+ClassicWindowTheme::~ClassicWindowTheme()
+{
+}
+
+Gfx::IntRect ClassicWindowTheme::title_bar_icon_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
+{
+ auto titlebar_rect = title_bar_rect(window_type, window_rect, palette);
+ Gfx::IntRect icon_rect {
+ titlebar_rect.x() + 2,
+ titlebar_rect.y(),
+ 16,
+ 16,
+ };
+ icon_rect.center_vertically_within(titlebar_rect);
+ icon_rect.move_by(0, 1);
+ return icon_rect;
+}
+
+Gfx::IntRect ClassicWindowTheme::title_bar_text_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
+{
+ auto titlebar_rect = title_bar_rect(window_type, window_rect, palette);
+ auto titlebar_icon_rect = title_bar_icon_rect(window_type, window_rect, palette);
+ return {
+ titlebar_rect.x() + 3 + titlebar_icon_rect.width() + 2,
+ titlebar_rect.y(),
+ titlebar_rect.width() - 5 - titlebar_icon_rect.width() - 2,
+ titlebar_rect.height()
+ };
+}
+
+void ClassicWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, const IntRect& window_rect, const StringView& title_text, const Bitmap& icon, const Palette& palette, const IntRect& leftmost_button_rect) const
+{
+ auto frame_rect = frame_rect_for_window(WindowType::Normal, window_rect, palette);
+ frame_rect.set_location({ 0, 0 });
+ Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
+
+ auto& title_font = FontDatabase::default_bold_font();
+
+ auto titlebar_rect = title_bar_rect(WindowType::Normal, window_rect, palette);
+ auto titlebar_icon_rect = title_bar_icon_rect(WindowType::Normal, window_rect, palette);
+ auto titlebar_inner_rect = title_bar_text_rect(WindowType::Normal, window_rect, palette);
+ auto titlebar_title_rect = titlebar_inner_rect;
+ titlebar_title_rect.set_width(FontDatabase::default_bold_font().width(title_text));
+
+ auto [title_color, border_color, border_color2, stripes_color, shadow_color] = compute_frame_colors(window_state, palette);
+
+ painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
+ painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
+
+ painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
+
+ int stripe_left = titlebar_title_rect.right() + 5;
+ int stripe_right = leftmost_button_rect.left() - 3;
+ if (stripe_left && stripe_right && stripe_left < stripe_right) {
+ for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
+ painter.draw_line({ stripe_left, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, stripes_color);
+ }
+ }
+
+ auto clipped_title_rect = titlebar_title_rect;
+ clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
+ if (!clipped_title_rect.is_empty()) {
+ painter.draw_text(clipped_title_rect.translated(1, 2), title_text, title_font, Gfx::TextAlignment::CenterLeft, shadow_color, Gfx::TextElision::Right);
+ // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
+ painter.draw_text(clipped_title_rect.translated(0, 1), title_text, title_font, Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
+ }
+
+ painter.blit(titlebar_icon_rect.location(), icon, icon.rect());
+}
+
+IntRect ClassicWindowTheme::title_bar_rect(WindowType window_type, const IntRect& window_rect, const Palette& palette) const
+{
+ auto& title_font = FontDatabase::default_bold_font();
+ auto window_titlebar_height = title_bar_height(palette);
+ // FIXME: The top of the titlebar doesn't get redrawn properly if this padding is different
+ int total_vertical_padding = title_font.glyph_height() - 1;
+
+ if (window_type == WindowType::Notification)
+ return { window_rect.width() + 3, total_vertical_padding / 2 - 1, window_titlebar_height, window_rect.height() };
+ return { 4, 4, window_rect.width(), window_titlebar_height };
+}
+
+ClassicWindowTheme::FrameColors ClassicWindowTheme::compute_frame_colors(WindowState state, const Palette& palette) const
+{
+ switch (state) {
+ case WindowState::Highlighted:
+ return { palette.highlight_window_title(), palette.highlight_window_border1(), palette.highlight_window_border2(), palette.highlight_window_title_stripes(), palette.highlight_window_title_shadow() };
+ case WindowState::Moving:
+ return { palette.moving_window_title(), palette.moving_window_border1(), palette.moving_window_border2(), palette.moving_window_title_stripes(), palette.moving_window_title_shadow() };
+ case WindowState::Active:
+ return { palette.active_window_title(), palette.active_window_border1(), palette.active_window_border2(), palette.active_window_title_stripes(), palette.active_window_title_shadow() };
+ case WindowState::Inactive:
+ return { palette.inactive_window_title(), palette.inactive_window_border1(), palette.inactive_window_border2(), palette.inactive_window_title_stripes(), palette.inactive_window_title_shadow() };
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void ClassicWindowTheme::paint_notification_frame(Painter& painter, const IntRect& window_rect, const Palette& palette, const IntRect& close_button_rect) const
+{
+ auto frame_rect = frame_rect_for_window(WindowType::Notification, window_rect, palette);
+ frame_rect.set_location({ 0, 0 });
+ Gfx::StylePainter::paint_window_frame(painter, frame_rect, palette);
+
+ auto titlebar_rect = title_bar_rect(WindowType::Notification, window_rect, palette);
+ painter.fill_rect_with_gradient(Gfx::Orientation::Vertical, titlebar_rect, palette.active_window_border1(), palette.active_window_border2());
+
+ int stripe_top = close_button_rect.bottom() + 4;
+ int stripe_bottom = window_rect.height() - 3;
+ if (stripe_top && stripe_bottom && stripe_top < stripe_bottom) {
+ for (int i = 2; i <= palette.window_title_height() - 2; i += 2) {
+ painter.draw_line({ titlebar_rect.x() + i, stripe_top }, { titlebar_rect.x() + i, stripe_bottom }, palette.active_window_title_stripes());
+ }
+ }
+}
+
+IntRect ClassicWindowTheme::frame_rect_for_window(WindowType window_type, const IntRect& window_rect, const Gfx::Palette& palette) const
+{
+ auto window_titlebar_height = title_bar_height(palette);
+
+ switch (window_type) {
+ case WindowType::Normal:
+ return {
+ window_rect.x() - 4,
+ window_rect.y() - window_titlebar_height - 6,
+ window_rect.width() + 8,
+ window_rect.height() + 10 + window_titlebar_height
+ };
+ case WindowType::Notification:
+ return {
+ window_rect.x() - 3,
+ window_rect.y() - 3,
+ window_rect.width() + 6 + window_titlebar_height,
+ window_rect.height() + 6
+ };
+ default:
+ return window_rect;
+ }
+}
+
+Vector<IntRect> ClassicWindowTheme::layout_buttons(WindowType window_type, const IntRect& window_rect, const Palette& palette, size_t buttons) const
+{
+ int window_button_width = palette.window_title_button_width();
+ int window_button_height = palette.window_title_button_height();
+ int pos;
+ Vector<IntRect> button_rects;
+ if (window_type == WindowType::Notification)
+ pos = title_bar_rect(window_type, window_rect, palette).top() + 2;
+ else
+ pos = title_bar_text_rect(window_type, window_rect, palette).right() + 1;
+
+ for (size_t i = 0; i < buttons; i++) {
+ if (window_type == WindowType::Notification) {
+ // The button height & width have to be equal or it leaks out of its area
+ Gfx::IntRect rect { 0, pos, window_button_height, window_button_height };
+ rect.center_horizontally_within(title_bar_rect(window_type, window_rect, palette));
+ button_rects.append(rect);
+ pos += window_button_height;
+ } else {
+ pos -= window_button_width;
+ Gfx::IntRect rect { pos, 0, window_button_width, window_button_height };
+ rect.center_vertically_within(title_bar_text_rect(window_type, window_rect, palette));
+ button_rects.append(rect);
+ }
+ }
+ return button_rects;
+}
+
+int ClassicWindowTheme::title_bar_height(const Palette& palette) const
+{
+ auto& title_font = FontDatabase::default_bold_font();
+ return max(palette.window_title_height(), title_font.glyph_height() + 8);
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ClassicWindowTheme.h b/Userland/Libraries/LibGfx/ClassicWindowTheme.h
new file mode 100644
index 0000000000..b6ae32e891
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ClassicWindowTheme.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Color.h>
+#include <LibGfx/WindowTheme.h>
+
+namespace Gfx {
+
+class ClassicWindowTheme final : public WindowTheme {
+public:
+ ClassicWindowTheme();
+ virtual ~ClassicWindowTheme() override;
+
+ virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect) const override;
+ virtual void paint_notification_frame(Painter&, const IntRect& window_rect, const Palette&, const IntRect& close_button_rect) const override;
+
+ virtual int title_bar_height(const Palette&) const override;
+ virtual IntRect title_bar_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
+ virtual IntRect title_bar_icon_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
+ virtual IntRect title_bar_text_rect(WindowType, const IntRect& window_rect, const Palette&) const override;
+
+ virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const override;
+
+ virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const override;
+
+private:
+ struct FrameColors {
+ Color title_color;
+ Color border_color;
+ Color border_color2;
+ Color title_stripes_color;
+ Color title_shadow_color;
+ };
+
+ FrameColors compute_frame_colors(WindowState, const Palette&) const;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Color.cpp b/Userland/Libraries/LibGfx/Color.cpp
new file mode 100644
index 0000000000..f8a0f244f4
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Color.cpp
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/SystemTheme.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Gfx {
+
+Color::Color(NamedColor named)
+{
+ if (named == Transparent) {
+ m_value = 0;
+ return;
+ }
+
+ struct {
+ u8 r;
+ u8 g;
+ u8 b;
+ } rgb;
+
+ switch (named) {
+ case Black:
+ rgb = { 0, 0, 0 };
+ break;
+ case White:
+ rgb = { 255, 255, 255 };
+ break;
+ case Red:
+ rgb = { 255, 0, 0 };
+ break;
+ case Green:
+ rgb = { 0, 255, 0 };
+ break;
+ case Cyan:
+ rgb = { 0, 255, 255 };
+ break;
+ case DarkCyan:
+ rgb = { 0, 127, 127 };
+ break;
+ case MidCyan:
+ rgb = { 0, 192, 192 };
+ break;
+ case Blue:
+ rgb = { 0, 0, 255 };
+ break;
+ case Yellow:
+ rgb = { 255, 255, 0 };
+ break;
+ case Magenta:
+ rgb = { 255, 0, 255 };
+ break;
+ case DarkGray:
+ rgb = { 64, 64, 64 };
+ break;
+ case MidGray:
+ rgb = { 127, 127, 127 };
+ break;
+ case LightGray:
+ rgb = { 192, 192, 192 };
+ break;
+ case MidGreen:
+ rgb = { 0, 192, 0 };
+ break;
+ case MidBlue:
+ rgb = { 0, 0, 192 };
+ break;
+ case MidRed:
+ rgb = { 192, 0, 0 };
+ break;
+ case MidMagenta:
+ rgb = { 192, 0, 192 };
+ break;
+ case DarkGreen:
+ rgb = { 0, 128, 0 };
+ break;
+ case DarkBlue:
+ rgb = { 0, 0, 128 };
+ break;
+ case DarkRed:
+ rgb = { 128, 0, 0 };
+ break;
+ case WarmGray:
+ rgb = { 212, 208, 200 };
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ m_value = 0xff000000 | (rgb.r << 16) | (rgb.g << 8) | rgb.b;
+}
+
+String Color::to_string() const
+{
+ return String::format("#%02x%02x%02x%02x", red(), green(), blue(), alpha());
+}
+
+String Color::to_string_without_alpha() const
+{
+ return String::format("#%02x%02x%02x", red(), green(), blue());
+}
+
+static Optional<Color> parse_rgb_color(const StringView& string)
+{
+ ASSERT(string.starts_with("rgb("));
+ ASSERT(string.ends_with(")"));
+
+ auto substring = string.substring_view(4, string.length() - 5);
+ auto parts = substring.split_view(',');
+
+ if (parts.size() != 3)
+ return {};
+
+ auto r = parts[0].to_uint().value_or(256);
+ auto g = parts[1].to_uint().value_or(256);
+ auto b = parts[2].to_uint().value_or(256);
+
+ if (r > 255 || g > 255 || b > 255)
+ return {};
+
+ return Color(r, g, b);
+}
+
+static Optional<Color> parse_rgba_color(const StringView& string)
+{
+ ASSERT(string.starts_with("rgba("));
+ ASSERT(string.ends_with(")"));
+
+ auto substring = string.substring_view(5, string.length() - 6);
+ auto parts = substring.split_view(',');
+
+ if (parts.size() != 4)
+ return {};
+
+ auto r = parts[0].to_int().value_or(256);
+ auto g = parts[1].to_int().value_or(256);
+ auto b = parts[2].to_int().value_or(256);
+
+ double alpha = strtod(parts[3].to_string().characters(), nullptr);
+ unsigned a = alpha * 255;
+
+ if (r > 255 || g > 255 || b > 255 || a > 255)
+ return {};
+
+ return Color(r, g, b, a);
+}
+
+Optional<Color> Color::from_string(const StringView& string)
+{
+ if (string.is_empty())
+ return {};
+
+ struct ColorAndWebName {
+ constexpr ColorAndWebName(RGBA32 c, const char* n)
+ : color(c)
+ , name(n)
+ {
+ }
+ RGBA32 color;
+ StringView name;
+ };
+
+ constexpr ColorAndWebName web_colors[] = {
+ // CSS Level 1
+ { 0x000000, "black" },
+ { 0xc0c0c0, "silver" },
+ { 0x808080, "gray" },
+ { 0xffffff, "white" },
+ { 0x800000, "maroon" },
+ { 0xff0000, "red" },
+ { 0x800080, "purple" },
+ { 0xff00ff, "fuchsia" },
+ { 0x008000, "green" },
+ { 0x00ff00, "lime" },
+ { 0x808000, "olive" },
+ { 0xffff00, "yellow" },
+ { 0x000080, "navy" },
+ { 0x0000ff, "blue" },
+ { 0x008080, "teal" },
+ { 0x00ffff, "aqua" },
+ // CSS Level 2 (Revision 1)
+ { 0xffa500, "orange" },
+ // CSS Color Module Level 3
+ { 0xf0f8ff, "aliceblue" },
+ { 0xfaebd7, "antiquewhite" },
+ { 0x7fffd4, "aquamarine" },
+ { 0xf0ffff, "azure" },
+ { 0xf5f5dc, "beige" },
+ { 0xffe4c4, "bisque" },
+ { 0xffebcd, "blanchedalmond" },
+ { 0x8a2be2, "blueviolet" },
+ { 0xa52a2a, "brown" },
+ { 0xdeb887, "burlywood" },
+ { 0x5f9ea0, "cadetblue" },
+ { 0x7fff00, "chartreuse" },
+ { 0xd2691e, "chocolate" },
+ { 0xff7f50, "coral" },
+ { 0x6495ed, "cornflowerblue" },
+ { 0xfff8dc, "cornsilk" },
+ { 0xdc143c, "crimson" },
+ { 0x00ffff, "cyan" },
+ { 0x00008b, "darkblue" },
+ { 0x008b8b, "darkcyan" },
+ { 0xb8860b, "darkgoldenrod" },
+ { 0xa9a9a9, "darkgray" },
+ { 0x006400, "darkgreen" },
+ { 0xa9a9a9, "darkgrey" },
+ { 0xbdb76b, "darkkhaki" },
+ { 0x8b008b, "darkmagenta" },
+ { 0x556b2f, "darkolivegreen" },
+ { 0xff8c00, "darkorange" },
+ { 0x9932cc, "darkorchid" },
+ { 0x8b0000, "darkred" },
+ { 0xe9967a, "darksalmon" },
+ { 0x8fbc8f, "darkseagreen" },
+ { 0x483d8b, "darkslateblue" },
+ { 0x2f4f4f, "darkslategray" },
+ { 0x2f4f4f, "darkslategrey" },
+ { 0x00ced1, "darkturquoise" },
+ { 0x9400d3, "darkviolet" },
+ { 0xff1493, "deeppink" },
+ { 0x00bfff, "deepskyblue" },
+ { 0x696969, "dimgray" },
+ { 0x696969, "dimgrey" },
+ { 0x1e90ff, "dodgerblue" },
+ { 0xb22222, "firebrick" },
+ { 0xfffaf0, "floralwhite" },
+ { 0x228b22, "forestgreen" },
+ { 0xdcdcdc, "gainsboro" },
+ { 0xf8f8ff, "ghostwhite" },
+ { 0xffd700, "gold" },
+ { 0xdaa520, "goldenrod" },
+ { 0xadff2f, "greenyellow" },
+ { 0x808080, "grey" },
+ { 0xf0fff0, "honeydew" },
+ { 0xff69b4, "hotpink" },
+ { 0xcd5c5c, "indianred" },
+ { 0x4b0082, "indigo" },
+ { 0xfffff0, "ivory" },
+ { 0xf0e68c, "khaki" },
+ { 0xe6e6fa, "lavender" },
+ { 0xfff0f5, "lavenderblush" },
+ { 0x7cfc00, "lawngreen" },
+ { 0xfffacd, "lemonchiffon" },
+ { 0xadd8e6, "lightblue" },
+ { 0xf08080, "lightcoral" },
+ { 0xe0ffff, "lightcyan" },
+ { 0xfafad2, "lightgoldenrodyellow" },
+ { 0xd3d3d3, "lightgray" },
+ { 0x90ee90, "lightgreen" },
+ { 0xd3d3d3, "lightgrey" },
+ { 0xffb6c1, "lightpink" },
+ { 0xffa07a, "lightsalmon" },
+ { 0x20b2aa, "lightseagreen" },
+ { 0x87cefa, "lightskyblue" },
+ { 0x778899, "lightslategray" },
+ { 0x778899, "lightslategrey" },
+ { 0xb0c4de, "lightsteelblue" },
+ { 0xffffe0, "lightyellow" },
+ { 0x32cd32, "limegreen" },
+ { 0xfaf0e6, "linen" },
+ { 0xff00ff, "magenta" },
+ { 0x66cdaa, "mediumaquamarine" },
+ { 0x0000cd, "mediumblue" },
+ { 0xba55d3, "mediumorchid" },
+ { 0x9370db, "mediumpurple" },
+ { 0x3cb371, "mediumseagreen" },
+ { 0x7b68ee, "mediumslateblue" },
+ { 0x00fa9a, "mediumspringgreen" },
+ { 0x48d1cc, "mediumturquoise" },
+ { 0xc71585, "mediumvioletred" },
+ { 0x191970, "midnightblue" },
+ { 0xf5fffa, "mintcream" },
+ { 0xffe4e1, "mistyrose" },
+ { 0xffe4b5, "moccasin" },
+ { 0xffdead, "navajowhite" },
+ { 0xfdf5e6, "oldlace" },
+ { 0x6b8e23, "olivedrab" },
+ { 0xff4500, "orangered" },
+ { 0xda70d6, "orchid" },
+ { 0xeee8aa, "palegoldenrod" },
+ { 0x98fb98, "palegreen" },
+ { 0xafeeee, "paleturquoise" },
+ { 0xdb7093, "palevioletred" },
+ { 0xffefd5, "papayawhip" },
+ { 0xffdab9, "peachpuff" },
+ { 0xcd853f, "peru" },
+ { 0xffc0cb, "pink" },
+ { 0xdda0dd, "plum" },
+ { 0xb0e0e6, "powderblue" },
+ { 0xbc8f8f, "rosybrown" },
+ { 0x4169e1, "royalblue" },
+ { 0x8b4513, "saddlebrown" },
+ { 0xfa8072, "salmon" },
+ { 0xf4a460, "sandybrown" },
+ { 0x2e8b57, "seagreen" },
+ { 0xfff5ee, "seashell" },
+ { 0xa0522d, "sienna" },
+ { 0x87ceeb, "skyblue" },
+ { 0x6a5acd, "slateblue" },
+ { 0x708090, "slategray" },
+ { 0x708090, "slategrey" },
+ { 0xfffafa, "snow" },
+ { 0x00ff7f, "springgreen" },
+ { 0x4682b4, "steelblue" },
+ { 0xd2b48c, "tan" },
+ { 0xd8bfd8, "thistle" },
+ { 0xff6347, "tomato" },
+ { 0x40e0d0, "turquoise" },
+ { 0xee82ee, "violet" },
+ { 0xf5deb3, "wheat" },
+ { 0xf5f5f5, "whitesmoke" },
+ { 0x9acd32, "yellowgreen" },
+ // CSS Color Module Level 4
+ { 0x663399, "rebeccapurple" },
+ // (Fallback)
+ { 0x000000, nullptr }
+ };
+
+ for (size_t i = 0; !web_colors[i].name.is_null(); ++i) {
+ if (string == web_colors[i].name)
+ return Color::from_rgb(web_colors[i].color);
+ }
+
+ if (string.starts_with("rgb(") && string.ends_with(")"))
+ return parse_rgb_color(string);
+
+ if (string.starts_with("rgba(") && string.ends_with(")"))
+ return parse_rgba_color(string);
+
+ if (string[0] != '#')
+ return {};
+
+ auto hex_nibble_to_u8 = [](char nibble) -> Optional<u8> {
+ if (!isxdigit(nibble))
+ return {};
+ if (nibble >= '0' && nibble <= '9')
+ return nibble - '0';
+ return 10 + (tolower(nibble) - 'a');
+ };
+
+ if (string.length() == 4) {
+ Optional<u8> r = hex_nibble_to_u8(string[1]);
+ Optional<u8> g = hex_nibble_to_u8(string[2]);
+ Optional<u8> b = hex_nibble_to_u8(string[3]);
+ if (!r.has_value() || !g.has_value() || !b.has_value())
+ return {};
+ return Color(r.value() * 17, g.value() * 17, b.value() * 17);
+ }
+
+ if (string.length() == 5) {
+ Optional<u8> r = hex_nibble_to_u8(string[1]);
+ Optional<u8> g = hex_nibble_to_u8(string[2]);
+ Optional<u8> b = hex_nibble_to_u8(string[3]);
+ Optional<u8> a = hex_nibble_to_u8(string[4]);
+ if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value())
+ return {};
+ return Color(r.value() * 17, g.value() * 17, b.value() * 17, a.value() * 17);
+ }
+
+ if (string.length() != 7 && string.length() != 9)
+ return {};
+
+ auto to_hex = [&](char c1, char c2) -> Optional<u8> {
+ auto nib1 = hex_nibble_to_u8(c1);
+ auto nib2 = hex_nibble_to_u8(c2);
+ if (!nib1.has_value() || !nib2.has_value())
+ return {};
+ return nib1.value() << 4 | nib2.value();
+ };
+
+ Optional<u8> r = to_hex(string[1], string[2]);
+ Optional<u8> g = to_hex(string[3], string[4]);
+ Optional<u8> b = to_hex(string[5], string[6]);
+ Optional<u8> a = string.length() == 9 ? to_hex(string[7], string[8]) : Optional<u8>(255);
+
+ if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value())
+ return {};
+
+ return Color(r.value(), g.value(), b.value(), a.value());
+}
+
+const LogStream& operator<<(const LogStream& stream, Color value)
+{
+ return stream << value.to_string();
+}
+}
+
+bool IPC::encode(IPC::Encoder& encoder, const Color& color)
+{
+ encoder << color.value();
+ return true;
+}
+
+bool IPC::decode(IPC::Decoder& decoder, Color& color)
+{
+ u32 rgba = 0;
+ if (!decoder.decode(rgba))
+ return false;
+ color = Color::from_rgba(rgba);
+ return true;
+}
+
+void AK::Formatter<Gfx::Color>::format(FormatBuilder& builder, const Gfx::Color& value)
+{
+ Formatter<StringView>::format(builder, value.to_string());
+}
diff --git a/Userland/Libraries/LibGfx/Color.h b/Userland/Libraries/LibGfx/Color.h
new file mode 100644
index 0000000000..e30cacba6c
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Color.h
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Assertions.h>
+#include <AK/Format.h>
+#include <AK/Forward.h>
+#include <AK/SIMD.h>
+#include <AK/StdLibExtras.h>
+#include <LibIPC/Forward.h>
+
+namespace Gfx {
+
+enum class ColorRole;
+typedef u32 RGBA32;
+
+constexpr u32 make_rgb(u8 r, u8 g, u8 b)
+{
+ return ((r << 16) | (g << 8) | b);
+}
+
+struct HSV {
+ double hue { 0 };
+ double saturation { 0 };
+ double value { 0 };
+};
+
+class Color {
+public:
+ enum NamedColor {
+ Transparent,
+ Black,
+ White,
+ Red,
+ Green,
+ Cyan,
+ Blue,
+ Yellow,
+ Magenta,
+ DarkGray,
+ MidGray,
+ LightGray,
+ WarmGray,
+ DarkCyan,
+ DarkGreen,
+ DarkBlue,
+ DarkRed,
+ MidCyan,
+ MidGreen,
+ MidRed,
+ MidBlue,
+ MidMagenta,
+ };
+
+ constexpr Color() { }
+ Color(NamedColor);
+ constexpr Color(u8 r, u8 g, u8 b)
+ : m_value(0xff000000 | (r << 16) | (g << 8) | b)
+ {
+ }
+ constexpr Color(u8 r, u8 g, u8 b, u8 a)
+ : m_value((a << 24) | (r << 16) | (g << 8) | b)
+ {
+ }
+
+ static constexpr Color from_rgb(unsigned rgb) { return Color(rgb | 0xff000000); }
+ static constexpr Color from_rgba(unsigned rgba) { return Color(rgba); }
+
+ constexpr u8 red() const { return (m_value >> 16) & 0xff; }
+ constexpr u8 green() const { return (m_value >> 8) & 0xff; }
+ constexpr u8 blue() const { return m_value & 0xff; }
+ constexpr u8 alpha() const { return (m_value >> 24) & 0xff; }
+
+ void set_alpha(u8 value)
+ {
+ m_value &= 0x00ffffff;
+ m_value |= value << 24;
+ }
+
+ constexpr void set_red(u8 value)
+ {
+ m_value &= 0xff00ffff;
+ m_value |= value << 16;
+ }
+
+ constexpr void set_green(u8 value)
+ {
+ m_value &= 0xffff00ff;
+ m_value |= value << 8;
+ }
+
+ constexpr void set_blue(u8 value)
+ {
+ m_value &= 0xffffff00;
+ m_value |= value;
+ }
+
+ Color with_alpha(u8 alpha) const
+ {
+ return Color((m_value & 0x00ffffff) | alpha << 24);
+ }
+
+ Color blend(Color source) const
+ {
+ if (!alpha() || source.alpha() == 255)
+ return source;
+
+ if (!source.alpha())
+ return *this;
+
+#ifdef __SSE__
+ using AK::SIMD::i32x4;
+
+ const i32x4 color = {
+ red(),
+ green(),
+ blue()
+ };
+ const i32x4 source_color = {
+ source.red(),
+ source.green(),
+ source.blue()
+ };
+
+ const int d = 255 * (alpha() + source.alpha()) - alpha() * source.alpha();
+ const i32x4 out = (color * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source_color) / d;
+ return Color(out[0], out[1], out[2], d / 255);
+#else
+ int d = 255 * (alpha() + source.alpha()) - alpha() * source.alpha();
+ u8 r = (red() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.red()) / d;
+ u8 g = (green() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.green()) / d;
+ u8 b = (blue() * alpha() * (255 - source.alpha()) + 255 * source.alpha() * source.blue()) / d;
+ u8 a = d / 255;
+ return Color(r, g, b, a);
+#endif
+ }
+
+ Color to_grayscale() const
+ {
+ int gray = (red() + green() + blue()) / 3;
+ return Color(gray, gray, gray, alpha());
+ }
+
+ Color darkened(float amount = 0.5f) const
+ {
+ return Color(red() * amount, green() * amount, blue() * amount, alpha());
+ }
+
+ Color lightened(float amount = 1.2f) const
+ {
+ return Color(min(255, (int)((float)red() * amount)), min(255, (int)((float)green() * amount)), min(255, (int)((float)blue() * amount)), alpha());
+ }
+
+ Color inverted() const
+ {
+ return Color(~red(), ~green(), ~blue(), alpha());
+ }
+
+ Color xored(const Color& other) const
+ {
+ return Color(((other.m_value ^ m_value) & 0x00ffffff) | (m_value & 0xff000000));
+ }
+
+ RGBA32 value() const { return m_value; }
+
+ bool operator==(const Color& other) const
+ {
+ return m_value == other.m_value;
+ }
+
+ bool operator!=(const Color& other) const
+ {
+ return m_value != other.m_value;
+ }
+
+ String to_string() const;
+ String to_string_without_alpha() const;
+ static Optional<Color> from_string(const StringView&);
+
+ HSV to_hsv() const
+ {
+ HSV hsv;
+ double r = static_cast<double>(red()) / 255.0;
+ double g = static_cast<double>(green()) / 255.0;
+ double b = static_cast<double>(blue()) / 255.0;
+ double max = AK::max(AK::max(r, g), b);
+ double min = AK::min(AK::min(r, g), b);
+ double chroma = max - min;
+
+ if (!chroma)
+ hsv.hue = 0.0;
+ else if (max == r)
+ hsv.hue = (60.0 * ((g - b) / chroma)) + 360.0;
+ else if (max == g)
+ hsv.hue = (60.0 * ((b - r) / chroma)) + 120.0;
+ else
+ hsv.hue = (60.0 * ((r - g) / chroma)) + 240.0;
+
+ if (hsv.hue >= 360.0)
+ hsv.hue -= 360.0;
+
+ if (!max)
+ hsv.saturation = 0;
+ else
+ hsv.saturation = chroma / max;
+
+ hsv.value = max;
+
+ ASSERT(hsv.hue >= 0.0 && hsv.hue < 360.0);
+ ASSERT(hsv.saturation >= 0.0 && hsv.saturation <= 1.0);
+ ASSERT(hsv.value >= 0.0 && hsv.value <= 1.0);
+
+ return hsv;
+ }
+
+ static Color from_hsv(double hue, double saturation, double value)
+ {
+ return from_hsv({ hue, saturation, value });
+ }
+
+ static Color from_hsv(const HSV& hsv)
+ {
+ ASSERT(hsv.hue >= 0.0 && hsv.hue < 360.0);
+ ASSERT(hsv.saturation >= 0.0 && hsv.saturation <= 1.0);
+ ASSERT(hsv.value >= 0.0 && hsv.value <= 1.0);
+
+ double hue = hsv.hue;
+ double saturation = hsv.saturation;
+ double value = hsv.value;
+
+ int high = static_cast<int>(hue / 60.0) % 6;
+ double f = (hue / 60.0) - high;
+ double c1 = value * (1.0 - saturation);
+ double c2 = value * (1.0 - saturation * f);
+ double c3 = value * (1.0 - saturation * (1.0 - f));
+
+ double r = 0;
+ double g = 0;
+ double b = 0;
+
+ switch (high) {
+ case 0:
+ r = value;
+ g = c3;
+ b = c1;
+ break;
+ case 1:
+ r = c2;
+ g = value;
+ b = c1;
+ break;
+ case 2:
+ r = c1;
+ g = value;
+ b = c3;
+ break;
+ case 3:
+ r = c1;
+ g = c2;
+ b = value;
+ break;
+ case 4:
+ r = c3;
+ g = c1;
+ b = value;
+ break;
+ case 5:
+ r = value;
+ g = c1;
+ b = c2;
+ break;
+ }
+
+ u8 out_r = (u8)(r * 255);
+ u8 out_g = (u8)(g * 255);
+ u8 out_b = (u8)(b * 255);
+ return Color(out_r, out_g, out_b);
+ }
+
+private:
+ constexpr explicit Color(RGBA32 rgba)
+ : m_value(rgba)
+ {
+ }
+
+ RGBA32 m_value { 0 };
+};
+
+const LogStream& operator<<(const LogStream&, Color);
+
+}
+
+using Gfx::Color;
+
+namespace AK {
+
+template<>
+struct Formatter<Gfx::Color> : public Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Color& value);
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::Color&);
+bool decode(Decoder&, Gfx::Color&);
+
+}
diff --git a/Userland/Libraries/LibGfx/DisjointRectSet.cpp b/Userland/Libraries/LibGfx/DisjointRectSet.cpp
new file mode 100644
index 0000000000..1ec3d95d90
--- /dev/null
+++ b/Userland/Libraries/LibGfx/DisjointRectSet.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/DisjointRectSet.h>
+
+namespace Gfx {
+
+bool DisjointRectSet::add_no_shatter(const IntRect& new_rect)
+{
+ if (new_rect.is_empty())
+ return false;
+ for (auto& rect : m_rects) {
+ if (rect.contains(new_rect))
+ return false;
+ }
+
+ m_rects.append(new_rect);
+ return true;
+}
+
+void DisjointRectSet::shatter()
+{
+ Vector<IntRect, 32> output;
+ output.ensure_capacity(m_rects.size());
+ bool pass_had_intersections = false;
+ do {
+ pass_had_intersections = false;
+ output.clear_with_capacity();
+ for (size_t i = 0; i < m_rects.size(); ++i) {
+ auto& r1 = m_rects[i];
+ for (size_t j = 0; j < m_rects.size(); ++j) {
+ if (i == j)
+ continue;
+ auto& r2 = m_rects[j];
+ if (!r1.intersects(r2))
+ continue;
+ pass_had_intersections = true;
+ auto pieces = r1.shatter(r2);
+ for (auto& piece : pieces)
+ output.append(piece);
+ m_rects.remove(i);
+ for (; i < m_rects.size(); ++i)
+ output.append(m_rects[i]);
+ goto next_pass;
+ }
+ output.append(r1);
+ }
+ next_pass:
+ swap(output, m_rects);
+ } while (pass_had_intersections);
+}
+
+void DisjointRectSet::move_by(int dx, int dy)
+{
+ for (auto& r : m_rects)
+ r.move_by(dx, dy);
+}
+
+bool DisjointRectSet::contains(const IntRect& rect) const
+{
+ if (is_empty() || rect.is_empty())
+ return false;
+
+ // TODO: This could use some optimization
+ DisjointRectSet remainder(rect);
+ for (auto& r : m_rects) {
+ auto shards = remainder.shatter(r);
+ if (shards.is_empty())
+ return true;
+ remainder = move(shards);
+ }
+ return false;
+}
+
+bool DisjointRectSet::intersects(const IntRect& rect) const
+{
+ for (auto& r : m_rects) {
+ if (r.intersects(rect))
+ return true;
+ }
+ return false;
+}
+
+bool DisjointRectSet::intersects(const DisjointRectSet& rects) const
+{
+ if (this == &rects)
+ return true;
+
+ for (auto& r : m_rects) {
+ for (auto& r2 : rects.m_rects) {
+ if (r.intersects(r2))
+ return true;
+ }
+ }
+ return false;
+}
+
+DisjointRectSet DisjointRectSet::intersected(const IntRect& rect) const
+{
+ DisjointRectSet intersected_rects;
+ intersected_rects.m_rects.ensure_capacity(m_rects.capacity());
+ for (auto& r : m_rects) {
+ auto intersected_rect = r.intersected(rect);
+ if (!intersected_rect.is_empty())
+ intersected_rects.m_rects.append(intersected_rect);
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return intersected_rects;
+}
+
+DisjointRectSet DisjointRectSet::intersected(const DisjointRectSet& rects) const
+{
+ if (&rects == this)
+ return clone();
+ if (is_empty() || rects.is_empty())
+ return {};
+
+ DisjointRectSet intersected_rects;
+ intersected_rects.m_rects.ensure_capacity(m_rects.capacity());
+ for (auto& r : m_rects) {
+ for (auto& r2 : rects.m_rects) {
+ auto intersected_rect = r.intersected(r2);
+ if (!intersected_rect.is_empty())
+ intersected_rects.m_rects.append(intersected_rect);
+ }
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return intersected_rects;
+}
+
+DisjointRectSet DisjointRectSet::shatter(const IntRect& hammer) const
+{
+ if (hammer.is_empty())
+ return clone();
+
+ DisjointRectSet shards;
+ for (auto& rect : m_rects) {
+ for (auto& shard : rect.shatter(hammer))
+ shards.add_no_shatter(shard);
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return shards;
+}
+
+DisjointRectSet DisjointRectSet::shatter(const DisjointRectSet& hammer) const
+{
+ if (this == &hammer)
+ return {};
+ if (hammer.is_empty() || !intersects(hammer))
+ return clone();
+
+ // TODO: This could use some optimization
+ DisjointRectSet shards = shatter(hammer.m_rects[0]);
+ auto rects_count = hammer.m_rects.size();
+ for (size_t i = 1; i < rects_count && !shards.is_empty(); i++) {
+ if (hammer.m_rects[i].intersects(shards.m_rects)) {
+ auto shattered = shards.shatter(hammer.m_rects[i]);
+ shards = move(shattered);
+ }
+ }
+ // Since there should be no overlaps, we don't need to call shatter()
+ return shards;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/DisjointRectSet.h b/Userland/Libraries/LibGfx/DisjointRectSet.h
new file mode 100644
index 0000000000..a54ce44ce3
--- /dev/null
+++ b/Userland/Libraries/LibGfx/DisjointRectSet.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+class DisjointRectSet {
+public:
+ DisjointRectSet(const DisjointRectSet&) = delete;
+ DisjointRectSet& operator=(const DisjointRectSet&) = delete;
+
+ DisjointRectSet() { }
+ ~DisjointRectSet() { }
+
+ DisjointRectSet(const IntRect& rect)
+ {
+ m_rects.append(rect);
+ }
+
+ DisjointRectSet(DisjointRectSet&&) = default;
+ DisjointRectSet& operator=(DisjointRectSet&&) = default;
+
+ DisjointRectSet clone() const
+ {
+ DisjointRectSet rects;
+ rects.m_rects = m_rects;
+ return rects;
+ }
+
+ void move_by(int dx, int dy);
+ void move_by(const IntPoint& delta)
+ {
+ move_by(delta.x(), delta.y());
+ }
+
+ void add(const IntRect& rect)
+ {
+ if (add_no_shatter(rect) && m_rects.size() > 1)
+ shatter();
+ }
+
+ template<typename Container>
+ void add_many(const Container& rects)
+ {
+ bool added = false;
+ for (const auto& rect : rects) {
+ if (add_no_shatter(rect))
+ added = true;
+ }
+ if (added && m_rects.size() > 1)
+ shatter();
+ }
+
+ void add(const DisjointRectSet& rect_set)
+ {
+ if (this == &rect_set)
+ return;
+ if (m_rects.is_empty()) {
+ m_rects = rect_set.m_rects;
+ } else {
+ add_many(rect_set.rects());
+ }
+ }
+
+ DisjointRectSet shatter(const IntRect&) const;
+ DisjointRectSet shatter(const DisjointRectSet& hammer) const;
+
+ bool contains(const IntRect&) const;
+ bool intersects(const IntRect&) const;
+ bool intersects(const DisjointRectSet&) const;
+ DisjointRectSet intersected(const IntRect&) const;
+ DisjointRectSet intersected(const DisjointRectSet&) const;
+
+ template<typename Function>
+ IterationDecision for_each_intersected(const IntRect& rect, Function f) const
+ {
+ if (is_empty() || rect.is_empty())
+ return IterationDecision::Continue;
+ for (auto& r : m_rects) {
+ auto intersected_rect = r.intersected(rect);
+ if (intersected_rect.is_empty())
+ continue;
+ IterationDecision decision = f(intersected_rect);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ return IterationDecision::Continue;
+ }
+
+ template<typename Function>
+ IterationDecision for_each_intersected(const DisjointRectSet& rects, Function f) const
+ {
+ if (is_empty() || rects.is_empty())
+ return IterationDecision::Continue;
+ if (this == &rects) {
+ for (auto& r : m_rects) {
+ IterationDecision decision = f(r);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ } else {
+ for (auto& r : m_rects) {
+ for (auto& r2 : rects.m_rects) {
+ auto intersected_rect = r.intersected(r2);
+ if (intersected_rect.is_empty())
+ continue;
+ IterationDecision decision = f(intersected_rect);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ }
+ }
+ return IterationDecision::Continue;
+ }
+
+ bool is_empty() const { return m_rects.is_empty(); }
+ size_t size() const { return m_rects.size(); }
+
+ void clear() { m_rects.clear(); }
+ void clear_with_capacity() { m_rects.clear_with_capacity(); }
+ const Vector<IntRect, 32>& rects() const { return m_rects; }
+ Vector<IntRect, 32> take_rects() { return move(m_rects); }
+
+private:
+ bool add_no_shatter(const IntRect&);
+ void shatter();
+
+ Vector<IntRect, 32> m_rects;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Emoji.cpp b/Userland/Libraries/LibGfx/Emoji.cpp
new file mode 100644
index 0000000000..5965f452fb
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Emoji.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Emoji.h>
+
+namespace Gfx {
+
+static HashMap<u32, RefPtr<Gfx::Bitmap>> s_emojis;
+
+const Bitmap* Emoji::emoji_for_code_point(u32 code_point)
+{
+ auto it = s_emojis.find(code_point);
+ if (it != s_emojis.end())
+ return (*it).value.ptr();
+
+ String path = String::format("/res/emoji/U+%X.png", code_point);
+
+ auto bitmap = Bitmap::load_from_file(path);
+ if (!bitmap) {
+ s_emojis.set(code_point, nullptr);
+ return nullptr;
+ }
+
+ s_emojis.set(code_point, bitmap);
+ return bitmap.ptr();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Emoji.h b/Userland/Libraries/LibGfx/Emoji.h
new file mode 100644
index 0000000000..232927732e
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Emoji.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace Gfx {
+
+class Bitmap;
+
+class Emoji {
+public:
+ static const Gfx::Bitmap* emoji_for_code_point(u32 code_point);
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h b/Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h
new file mode 100644
index 0000000000..8be43befe1
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/BoxBlurFilter.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+
+namespace Gfx {
+
+template<size_t N>
+class BoxBlurFilter : public GenericConvolutionFilter<N> {
+public:
+ BoxBlurFilter() { }
+ virtual ~BoxBlurFilter() { }
+
+ virtual const char* class_name() const override { return "BoxBlurFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/Filter.h b/Userland/Libraries/LibGfx/Filters/Filter.h
new file mode 100644
index 0000000000..e0ea811d4a
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/Filter.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+class Filter {
+public:
+ class Parameters {
+ public:
+ virtual bool is_generic_convolution_filter() const { return false; }
+
+ virtual ~Parameters() { }
+ };
+ virtual ~Filter() { }
+
+ virtual const char* class_name() const = 0;
+
+ virtual void apply(Bitmap&, const IntRect&, const Bitmap&, const IntRect&, const Parameters&) = 0;
+
+protected:
+ Filter() { }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h b/Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h
new file mode 100644
index 0000000000..416f84321a
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/GenericConvolutionFilter.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Filter.h"
+#include <LibGfx/Matrix.h>
+#include <LibGfx/Matrix4x4.h>
+
+namespace Gfx {
+
+template<size_t N, typename T>
+inline static constexpr void normalize(Matrix<N, T>& matrix)
+{
+ auto sum = 0.0f;
+ for (size_t i = 0; i < matrix.Size; ++i) {
+ for (size_t j = 0; j < matrix.Size; ++j) {
+ sum += matrix.elements()[i][j];
+ }
+ }
+ for (size_t i = 0; i < matrix.Size; ++i) {
+ for (size_t j = 0; j < matrix.Size; ++j) {
+ matrix.elements()[i][j] /= sum;
+ }
+ }
+}
+
+template<size_t N>
+class GenericConvolutionFilter : public Filter {
+public:
+ class Parameters : public Filter::Parameters {
+ public:
+ Parameters(const Gfx::Matrix<N, float>& kernel, bool should_wrap = false)
+ : m_kernel(kernel)
+ , m_should_wrap(should_wrap)
+
+ {
+ }
+
+ const Gfx::Matrix<N, float>& kernel() const { return m_kernel; }
+ Gfx::Matrix<N, float>& kernel() { return m_kernel; }
+ bool should_wrap() const { return m_should_wrap; }
+
+ private:
+ virtual bool is_generic_convolution_filter() const override { return true; }
+ Gfx::Matrix<N, float> m_kernel;
+ bool m_should_wrap { false };
+ };
+
+ class ApplyCache {
+ template<size_t>
+ friend class GenericConvolutionFilter;
+
+ private:
+ RefPtr<Gfx::Bitmap> m_target;
+ };
+
+ GenericConvolutionFilter() { }
+ virtual ~GenericConvolutionFilter() { }
+
+ virtual const char* class_name() const override { return "GenericConvolutionFilter"; }
+
+ virtual void apply(Bitmap& target_bitmap, const IntRect& target_rect, const Bitmap& source_bitmap, const IntRect& source_rect, const Filter::Parameters& parameters) override
+ {
+ ASSERT(parameters.is_generic_convolution_filter());
+ auto& gcf_params = static_cast<const GenericConvolutionFilter::Parameters&>(parameters);
+
+ ApplyCache apply_cache;
+ apply(target_bitmap, target_rect, source_bitmap, source_rect, gcf_params, apply_cache);
+ }
+
+ void apply(Bitmap& target, IntRect target_rect, const Bitmap& source, const IntRect& source_rect, const GenericConvolutionFilter::Parameters& parameters, ApplyCache& apply_cache)
+ {
+ // The target area (where the filter is applied) must be entirely
+ // contained by the source area. source_rect should be describing
+ // the pixels that can be accessed to apply this filter, while
+ // target_rect should describe the area where to apply the filter on.
+ ASSERT(source_rect.contains(target_rect));
+ ASSERT(source.size().contains(target.size()));
+ ASSERT(target.rect().contains(target_rect));
+ ASSERT(source.rect().contains(source_rect));
+
+ // If source is different from target, it should still be describing
+ // essentially the same bitmap. But it allows us to modify target
+ // without a temporary bitmap. This is important if this filter
+ // is applied on multiple areas of the same bitmap, at which point
+ // we would need to be able to access unmodified pixels if the
+ // areas are (almost) adjacent.
+ int source_delta_x = target_rect.x() - source_rect.x();
+ int source_delta_y = target_rect.y() - source_rect.y();
+ if (&target == &source && (!apply_cache.m_target || !apply_cache.m_target->size().contains(source_rect.size()))) {
+ // TODO: We probably don't need the entire source_rect, we could inflate
+ // the target_rect appropriately
+ apply_cache.m_target = Gfx::Bitmap::create(source.format(), source_rect.size());
+ target_rect.move_by(-target_rect.location());
+ }
+
+ Bitmap* render_target_bitmap = (&target != &source) ? &target : apply_cache.m_target.ptr();
+
+ // FIXME: Help! I am naive!
+ constexpr static ssize_t offset = N / 2;
+ for (auto i_ = 0; i_ < target_rect.width(); ++i_) {
+ ssize_t i = i_ + target_rect.x();
+ for (auto j_ = 0; j_ < target_rect.height(); ++j_) {
+ ssize_t j = j_ + target_rect.y();
+ FloatVector3 value(0, 0, 0);
+ for (auto k = 0l; k < (ssize_t)N; ++k) {
+ auto ki = i + k - offset;
+ if (ki < source_rect.x() || ki > source_rect.right()) {
+ if (parameters.should_wrap())
+ ki = (ki + source.size().width()) % source.size().width(); // TODO: fix up using source_rect
+ else
+ continue;
+ }
+
+ for (auto l = 0l; l < (ssize_t)N; ++l) {
+ auto lj = j + l - offset;
+ if (lj < source_rect.y() || lj > source_rect.bottom()) {
+ if (parameters.should_wrap())
+ lj = (lj + source.size().height()) % source.size().height(); // TODO: fix up using source_rect
+ else
+ continue;
+ }
+
+ auto pixel = source.get_pixel(ki, lj);
+ FloatVector3 pixel_value(pixel.red(), pixel.green(), pixel.blue());
+
+ value = value + pixel_value * parameters.kernel().elements()[k][l];
+ }
+ }
+
+ value.clamp(0, 255);
+ render_target_bitmap->set_pixel(i, j, Color(value.x(), value.y(), value.z(), source.get_pixel(i + source_delta_x, j + source_delta_y).alpha()));
+ }
+ }
+
+ if (render_target_bitmap != &target) {
+ // FIXME: Substitute for some sort of faster "blit" method.
+ for (auto i_ = 0; i_ < target_rect.width(); ++i_) {
+ auto i = i_ + target_rect.x();
+ for (auto j_ = 0; j_ < target_rect.height(); ++j_) {
+ auto j = j_ + target_rect.y();
+ target.set_pixel(i, j, render_target_bitmap->get_pixel(i_, j_));
+ }
+ }
+ }
+ }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/LaplacianFilter.h b/Userland/Libraries/LibGfx/Filters/LaplacianFilter.h
new file mode 100644
index 0000000000..fe1c247582
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/LaplacianFilter.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+
+namespace Gfx {
+
+class LaplacianFilter : public GenericConvolutionFilter<3> {
+public:
+ LaplacianFilter() { }
+ virtual ~LaplacianFilter() { }
+
+ virtual const char* class_name() const override { return "LaplacianFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/SharpenFilter.h b/Userland/Libraries/LibGfx/Filters/SharpenFilter.h
new file mode 100644
index 0000000000..25456aa691
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/SharpenFilter.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+
+namespace Gfx {
+
+class SharpenFilter : public GenericConvolutionFilter<3> {
+public:
+ SharpenFilter() { }
+ virtual ~SharpenFilter() { }
+
+ virtual const char* class_name() const override { return "SharpenFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h b/Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h
new file mode 100644
index 0000000000..8bf8d414ca
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Filters/SpatialGaussianBlurFilter.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GenericConvolutionFilter.h"
+#include <AK/StdLibExtras.h>
+
+namespace Gfx {
+
+template<size_t N, typename = typename AK::EnableIf<N % 2 == 1>::Type>
+class SpatialGaussianBlurFilter : public GenericConvolutionFilter<N> {
+public:
+ SpatialGaussianBlurFilter() { }
+ virtual ~SpatialGaussianBlurFilter() { }
+
+ virtual const char* class_name() const override { return "SpatialGaussianBlurFilter"; }
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Font.cpp b/Userland/Libraries/LibGfx/Font.cpp
new file mode 100644
index 0000000000..ba0849b21b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Font.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/BitmapFont.h>
+#include <LibGfx/Font.h>
+
+namespace Gfx {
+
+RefPtr<Font> Font::load_from_file(const StringView& path)
+{
+ if (path.ends_with(".font")) {
+ return BitmapFont::load_from_file(path);
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Font.h b/Userland/Libraries/LibGfx/Font.h
new file mode 100644
index 0000000000..2e74798725
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Font.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/MappedFile.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+// FIXME: Make a MutableGlyphBitmap buddy class for FontEditor instead?
+class GlyphBitmap {
+ friend class BitmapFont;
+
+public:
+ const unsigned* rows() const { return m_rows; }
+ unsigned row(unsigned index) const { return m_rows[index]; }
+
+ bool bit_at(int x, int y) const { return row(y) & (1 << x); }
+ void set_bit_at(int x, int y, bool b)
+ {
+ auto& mutable_row = const_cast<unsigned*>(m_rows)[y];
+ if (b)
+ mutable_row |= 1 << x;
+ else
+ mutable_row &= ~(1 << x);
+ }
+
+ IntSize size() const { return m_size; }
+ int width() const { return m_size.width(); }
+ int height() const { return m_size.height(); }
+
+private:
+ GlyphBitmap(const unsigned* rows, IntSize size)
+ : m_rows(rows)
+ , m_size(size)
+ {
+ }
+
+ const unsigned* m_rows { nullptr };
+ IntSize m_size;
+};
+
+class Font : public RefCounted<Font> {
+public:
+ static RefPtr<Font> load_from_file(const StringView& path);
+
+ virtual NonnullRefPtr<Font> clone() const = 0;
+ virtual ~Font() {};
+
+ virtual u8 presentation_size() const = 0;
+
+ virtual u16 weight() const = 0;
+ virtual GlyphBitmap glyph_bitmap(u32 code_point) const = 0;
+
+ virtual u8 glyph_width(size_t ch) const = 0;
+ virtual int glyph_or_emoji_width(u32 code_point) const = 0;
+ virtual u8 glyph_height() const = 0;
+ virtual int x_height() const = 0;
+
+ virtual u8 min_glyph_width() const = 0;
+ virtual u8 max_glyph_width() const = 0;
+ virtual u8 glyph_fixed_width() const = 0;
+
+ virtual u8 baseline() const = 0;
+ virtual u8 mean_line() const = 0;
+
+ virtual int width(const StringView&) const = 0;
+ virtual int width(const Utf8View&) const = 0;
+ virtual int width(const Utf32View&) const = 0;
+
+ virtual const String& name() const = 0;
+
+ virtual bool is_fixed_width() const = 0;
+
+ virtual u8 glyph_spacing() const = 0;
+
+ virtual int glyph_count() const = 0;
+
+ virtual const String& family() const = 0;
+
+ virtual String qualified_name() const = 0;
+
+ virtual const Font& bold_variant() const = 0;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/FontDatabase.cpp b/Userland/Libraries/LibGfx/FontDatabase.cpp
new file mode 100644
index 0000000000..4fe466ece9
--- /dev/null
+++ b/Userland/Libraries/LibGfx/FontDatabase.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/QuickSort.h>
+#include <LibCore/DirIterator.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Gfx {
+
+static FontDatabase* s_the;
+
+FontDatabase& FontDatabase::the()
+{
+ if (!s_the)
+ s_the = new FontDatabase;
+ return *s_the;
+}
+
+Font& FontDatabase::default_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Katica 10 400");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+Font& FontDatabase::default_fixed_width_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Csilla 10 400");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+Font& FontDatabase::default_bold_fixed_width_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Csilla 10 700");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+Font& FontDatabase::default_bold_font()
+{
+ static Font* font;
+ if (!font) {
+ font = FontDatabase::the().get_by_name("Katica 10 700");
+ ASSERT(font);
+ }
+ return *font;
+}
+
+struct FontDatabase::Private {
+ HashMap<String, RefPtr<Gfx::Font>> full_name_to_font_map;
+};
+
+FontDatabase::FontDatabase()
+ : m_private(make<Private>())
+{
+ Core::DirIterator di("/res/fonts", Core::DirIterator::SkipDots);
+ if (di.has_error()) {
+ fprintf(stderr, "DirIterator: %s\n", di.error_string());
+ exit(1);
+ }
+ while (di.has_next()) {
+ String name = di.next_path();
+ if (!name.ends_with(".font"))
+ continue;
+
+ auto path = String::format("/res/fonts/%s", name.characters());
+ if (auto font = Gfx::Font::load_from_file(path)) {
+ m_private->full_name_to_font_map.set(font->qualified_name(), font);
+ }
+ }
+}
+
+FontDatabase::~FontDatabase()
+{
+}
+
+void FontDatabase::for_each_font(Function<void(const Gfx::Font&)> callback)
+{
+ Vector<RefPtr<Gfx::Font>> fonts;
+ fonts.ensure_capacity(m_private->full_name_to_font_map.size());
+ for (auto& it : m_private->full_name_to_font_map)
+ fonts.append(it.value);
+ quick_sort(fonts, [](auto& a, auto& b) { return a->qualified_name() < b->qualified_name(); });
+ for (auto& font : fonts)
+ callback(*font);
+}
+
+void FontDatabase::for_each_fixed_width_font(Function<void(const Gfx::Font&)> callback)
+{
+ Vector<RefPtr<Gfx::Font>> fonts;
+ fonts.ensure_capacity(m_private->full_name_to_font_map.size());
+ for (auto& it : m_private->full_name_to_font_map) {
+ if (it.value->is_fixed_width())
+ fonts.append(it.value);
+ }
+ quick_sort(fonts, [](auto& a, auto& b) { return a->qualified_name() < b->qualified_name(); });
+ for (auto& font : fonts)
+ callback(*font);
+}
+
+RefPtr<Gfx::Font> FontDatabase::get_by_name(const StringView& name)
+{
+ auto it = m_private->full_name_to_font_map.find(name);
+ if (it == m_private->full_name_to_font_map.end()) {
+ dbgln("Font lookup failed: '{}'", name);
+ return nullptr;
+ }
+ return it->value;
+}
+
+RefPtr<Gfx::Font> FontDatabase::get(const String& family, unsigned size, unsigned weight)
+{
+ for (auto& it : m_private->full_name_to_font_map) {
+ auto& font = *it.value;
+ if (font.family() == family && font.presentation_size() == size && font.weight() == weight)
+ return font;
+ }
+ return nullptr;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/FontDatabase.h b/Userland/Libraries/LibGfx/FontDatabase.h
new file mode 100644
index 0000000000..e1c21a20a3
--- /dev/null
+++ b/Userland/Libraries/LibGfx/FontDatabase.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+class FontDatabase {
+public:
+ static FontDatabase& the();
+
+ static Font& default_font();
+ static Font& default_bold_font();
+
+ static Font& default_fixed_width_font();
+ static Font& default_bold_fixed_width_font();
+
+ RefPtr<Gfx::Font> get(const String& family, unsigned size, unsigned weight);
+ RefPtr<Gfx::Font> get_by_name(const StringView&);
+ void for_each_font(Function<void(const Gfx::Font&)>);
+ void for_each_fixed_width_font(Function<void(const Gfx::Font&)>);
+
+private:
+ FontDatabase();
+ ~FontDatabase();
+
+ struct Private;
+ OwnPtr<Private> m_private;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Forward.h b/Userland/Libraries/LibGfx/Forward.h
new file mode 100644
index 0000000000..7d7fc2f482
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Forward.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+class Bitmap;
+class CharacterBitmap;
+class Color;
+class DisjointRectSet;
+class Emoji;
+class Font;
+class GlyphBitmap;
+class ImageDecoder;
+class Painter;
+class Palette;
+class PaletteImpl;
+class Path;
+class ShareableBitmap;
+class StylePainter;
+struct SystemTheme;
+class Triangle;
+
+template<typename T>
+class Point;
+
+template<typename T>
+class Size;
+
+template<typename T>
+class Rect;
+
+using IntRect = Rect<int>;
+using FloatRect = Rect<float>;
+
+using IntPoint = Point<int>;
+using FloatPoint = Point<float>;
+
+using IntSize = Size<int>;
+using FloatSize = Size<float>;
+
+enum class BitmapFormat;
+enum class ColorRole;
+enum class TextAlignment;
+
+}
+
+using Gfx::Color;
diff --git a/Userland/Libraries/LibGfx/GIFLoader.cpp b/Userland/Libraries/LibGfx/GIFLoader.cpp
new file mode 100644
index 0000000000..d29c5969e3
--- /dev/null
+++ b/Userland/Libraries/LibGfx/GIFLoader.cpp
@@ -0,0 +1,765 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Array.h>
+#include <AK/ByteBuffer.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/MemoryStream.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibGfx/GIFLoader.h>
+#include <LibGfx/Painter.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+//#define GIF_DEBUG
+
+namespace Gfx {
+
+// Row strides and offsets for each interlace pass.
+static const int INTERLACE_ROW_STRIDES[] = { 8, 8, 4, 2 };
+static const int INTERLACE_ROW_OFFSETS[] = { 0, 4, 2, 1 };
+
+struct ImageDescriptor {
+ u16 x { 0 };
+ u16 y { 0 };
+ u16 width { 0 };
+ u16 height { 0 };
+ bool use_global_color_map { true };
+ bool interlaced { false };
+ Color color_map[256];
+ u8 lzw_min_code_size { 0 };
+ Vector<u8> lzw_encoded_bytes;
+
+ // Fields from optional graphic control extension block
+ enum DisposalMethod : u8 {
+ None = 0,
+ InPlace = 1,
+ RestoreBackground = 2,
+ RestorePrevious = 3,
+ };
+ DisposalMethod disposal_method { None };
+ u8 transparency_index { 0 };
+ u16 duration { 0 };
+ bool transparent { false };
+ bool user_input { false };
+
+ const IntRect rect() const
+ {
+ return { this->x, this->y, this->width, this->height };
+ }
+};
+
+struct LogicalScreen {
+ u16 width;
+ u16 height;
+ Color color_map[256];
+};
+
+struct GIFLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ FrameDescriptorsLoaded,
+ FrameComplete,
+ };
+ State state { NotDecoded };
+ enum ErrorState {
+ NoError = 0,
+ FailedToDecodeAllFrames,
+ FailedToDecodeAnyFrame,
+ FailedToLoadFrameDescriptors,
+ };
+ ErrorState error_state { NoError };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ LogicalScreen logical_screen {};
+ u8 background_color_index { 0 };
+ NonnullOwnPtrVector<ImageDescriptor> images {};
+ size_t loops { 1 };
+ RefPtr<Gfx::Bitmap> frame_buffer;
+ size_t current_frame { 0 };
+ RefPtr<Gfx::Bitmap> prev_frame_buffer;
+};
+
+RefPtr<Gfx::Bitmap> load_gif(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ GIFImageDecoderPlugin gif_decoder((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ auto bitmap = gif_decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded GIF: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_gif_from_memory(const u8* data, size_t length)
+{
+ GIFImageDecoderPlugin gif_decoder(data, length);
+ auto bitmap = gif_decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded GIF: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+enum class GIFFormat {
+ GIF87a,
+ GIF89a,
+};
+
+static Optional<GIFFormat> decode_gif_header(InputMemoryStream& stream)
+{
+ static const char valid_header_87[] = "GIF87a";
+ static const char valid_header_89[] = "GIF89a";
+
+ Array<u8, 6> header;
+ stream >> header;
+
+ if (stream.handle_any_error())
+ return {};
+
+ if (header.span() == ReadonlyBytes { valid_header_87, 6 })
+ return GIFFormat::GIF87a;
+ if (header.span() == ReadonlyBytes { valid_header_89, 6 })
+ return GIFFormat::GIF89a;
+
+ return {};
+}
+
+class LZWDecoder {
+private:
+ static constexpr int max_code_size = 12;
+
+public:
+ explicit LZWDecoder(const Vector<u8>& lzw_bytes, u8 min_code_size)
+ : m_lzw_bytes(lzw_bytes)
+ , m_code_size(min_code_size)
+ , m_original_code_size(min_code_size)
+ , m_table_capacity(pow(2, min_code_size))
+ {
+ init_code_table();
+ }
+
+ u16 add_control_code()
+ {
+ const u16 control_code = m_code_table.size();
+ m_code_table.append(Vector<u8> {});
+ m_original_code_table.append(Vector<u8> {});
+ if (m_code_table.size() >= m_table_capacity && m_code_size < max_code_size) {
+
+ ++m_code_size;
+ ++m_original_code_size;
+ m_table_capacity *= 2;
+ }
+ return control_code;
+ }
+
+ void reset()
+ {
+ m_code_table.clear();
+ m_code_table.append(m_original_code_table);
+ m_code_size = m_original_code_size;
+ m_table_capacity = pow(2, m_code_size);
+ m_output.clear();
+ }
+
+ Optional<u16> next_code()
+ {
+ size_t current_byte_index = m_current_bit_index / 8;
+ if (current_byte_index >= m_lzw_bytes.size()) {
+ return {};
+ }
+
+ // Extract the code bits using a 32-bit mask to cover the possibility that if
+ // the current code size > 9 bits then the code can span 3 bytes.
+ u8 current_bit_offset = m_current_bit_index % 8;
+ u32 mask = (u32)(m_table_capacity - 1) << current_bit_offset;
+
+ // Make a padded copy of the final bytes in the data to ensure we don't read past the end.
+ if (current_byte_index + sizeof(mask) > m_lzw_bytes.size()) {
+ u8 padded_last_bytes[sizeof(mask)] = { 0 };
+ for (int i = 0; current_byte_index + i < m_lzw_bytes.size(); ++i) {
+ padded_last_bytes[i] = m_lzw_bytes[current_byte_index + i];
+ }
+ const u32* addr = (const u32*)&padded_last_bytes;
+ m_current_code = (*addr & mask) >> current_bit_offset;
+ } else {
+ const u32* addr = (const u32*)&m_lzw_bytes.at(current_byte_index);
+ m_current_code = (*addr & mask) >> current_bit_offset;
+ }
+
+ if (m_current_code > m_code_table.size()) {
+#ifdef GIF_DEBUG
+ dbg() << "Corrupted LZW stream, invalid code: " << m_current_code << " at bit index: "
+ << m_current_bit_index << ", code table size: " << m_code_table.size();
+#endif
+ return {};
+ } else if (m_current_code == m_code_table.size() && m_output.is_empty()) {
+#ifdef GIF_DEBUG
+ dbg() << "Corrupted LZW stream, valid new code but output buffer is empty: " << m_current_code
+ << " at bit index: " << m_current_bit_index << ", code table size: " << m_code_table.size();
+#endif
+ return {};
+ }
+
+ m_current_bit_index += m_code_size;
+
+ return m_current_code;
+ }
+
+ Vector<u8>& get_output()
+ {
+ ASSERT(m_current_code <= m_code_table.size());
+ if (m_current_code < m_code_table.size()) {
+ Vector<u8> new_entry = m_output;
+ m_output = m_code_table.at(m_current_code);
+ new_entry.append(m_output[0]);
+ extend_code_table(new_entry);
+ } else if (m_current_code == m_code_table.size()) {
+ ASSERT(!m_output.is_empty());
+ m_output.append(m_output[0]);
+ extend_code_table(m_output);
+ }
+ return m_output;
+ }
+
+private:
+ void init_code_table()
+ {
+ m_code_table.clear();
+ for (u16 i = 0; i < m_table_capacity; ++i) {
+ m_code_table.append({ (u8)i });
+ }
+ m_original_code_table = m_code_table;
+ }
+
+ void extend_code_table(const Vector<u8>& entry)
+ {
+ if (entry.size() > 1 && m_code_table.size() < 4096) {
+ m_code_table.append(entry);
+ if (m_code_table.size() >= m_table_capacity && m_code_size < max_code_size) {
+ ++m_code_size;
+ m_table_capacity *= 2;
+ }
+ }
+ }
+
+ const Vector<u8>& m_lzw_bytes;
+
+ int m_current_bit_index { 0 };
+
+ Vector<Vector<u8>> m_code_table {};
+ Vector<Vector<u8>> m_original_code_table {};
+
+ u8 m_code_size { 0 };
+ u8 m_original_code_size { 0 };
+
+ u32 m_table_capacity { 0 };
+
+ u16 m_current_code { 0 };
+ Vector<u8> m_output {};
+};
+
+static void copy_frame_buffer(Bitmap& dest, const Bitmap& src)
+{
+ ASSERT(dest.size_in_bytes() == src.size_in_bytes());
+ memcpy(dest.scanline(0), src.scanline(0), dest.size_in_bytes());
+}
+
+static bool decode_frame(GIFLoadingContext& context, size_t frame_index)
+{
+ if (frame_index >= context.images.size()) {
+ return false;
+ }
+
+ if (context.state >= GIFLoadingContext::State::FrameComplete && frame_index == context.current_frame) {
+ return true;
+ }
+
+ size_t start_frame = context.current_frame + 1;
+ if (context.state < GIFLoadingContext::State::FrameComplete) {
+ start_frame = 0;
+ context.frame_buffer = Bitmap::create_purgeable(BitmapFormat::RGBA32, { context.logical_screen.width, context.logical_screen.height });
+ if (!context.frame_buffer)
+ return false;
+ context.prev_frame_buffer = Bitmap::create_purgeable(BitmapFormat::RGBA32, { context.logical_screen.width, context.logical_screen.height });
+ if (!context.prev_frame_buffer)
+ return false;
+ } else if (frame_index < context.current_frame) {
+ start_frame = 0;
+ }
+
+ for (size_t i = start_frame; i <= frame_index; ++i) {
+ auto& image = context.images.at(i);
+
+ const auto previous_image_disposal_method = i > 0 ? context.images.at(i - 1).disposal_method : ImageDescriptor::DisposalMethod::None;
+
+ if (i == 0) {
+ context.frame_buffer->fill(Color::Transparent);
+ } else if (i > 0 && image.disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious
+ && previous_image_disposal_method != ImageDescriptor::DisposalMethod::RestorePrevious) {
+ // This marks the start of a run of frames that once disposed should be restored to the
+ // previous underlying image contents. Therefore we make a copy of the current frame
+ // buffer so that it can be restored later.
+ copy_frame_buffer(*context.prev_frame_buffer, *context.frame_buffer);
+ }
+
+ if (previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestoreBackground) {
+ // Note: RestoreBackground could be interpreted either as restoring the underlying
+ // background of the entire image (e.g. container element's background-color), or the
+ // background color of the GIF itself. It appears that all major browsers and most other
+ // GIF decoders adhere to the former interpretation, therefore we will do the same by
+ // clearing the entire frame buffer to transparent.
+ Painter painter(*context.frame_buffer);
+ painter.clear_rect(context.images.at(i - 1).rect(), Color::Transparent);
+ } else if (i > 0 && previous_image_disposal_method == ImageDescriptor::DisposalMethod::RestorePrevious) {
+ // Previous frame indicated that once disposed, it should be restored to *its* previous
+ // underlying image contents, therefore we restore the saved previous frame buffer.
+ copy_frame_buffer(*context.frame_buffer, *context.prev_frame_buffer);
+ }
+
+ LZWDecoder decoder(image.lzw_encoded_bytes, image.lzw_min_code_size);
+
+ // Add GIF-specific control codes
+ const int clear_code = decoder.add_control_code();
+ const int end_of_information_code = decoder.add_control_code();
+
+ const auto& color_map = image.use_global_color_map ? context.logical_screen.color_map : image.color_map;
+
+ int pixel_index = 0;
+ int row = 0;
+ int interlace_pass = 0;
+ while (true) {
+ Optional<u16> code = decoder.next_code();
+ if (!code.has_value()) {
+#ifdef GIF_DEBUG
+ dbgln("Unexpectedly reached end of gif frame data");
+#endif
+ return false;
+ }
+
+ if (code.value() == clear_code) {
+ decoder.reset();
+ continue;
+ }
+ if (code.value() == end_of_information_code)
+ break;
+ if (!image.width)
+ continue;
+
+ auto colors = decoder.get_output();
+ for (const auto& color : colors) {
+ auto c = color_map[color];
+
+ int x = pixel_index % image.width + image.x;
+ int y = row + image.y;
+
+ if (context.frame_buffer->rect().contains(x, y) && (!image.transparent || color != image.transparency_index)) {
+ context.frame_buffer->set_pixel(x, y, c);
+ }
+
+ ++pixel_index;
+ if (pixel_index % image.width == 0) {
+ if (image.interlaced) {
+ if (row + INTERLACE_ROW_STRIDES[interlace_pass] >= image.height) {
+ ++interlace_pass;
+ // FIXME: We could probably figure this out earlier and fail before doing a bunch of work.
+ if (interlace_pass >= 4)
+ return false;
+ row = INTERLACE_ROW_OFFSETS[interlace_pass];
+ } else {
+ row += INTERLACE_ROW_STRIDES[interlace_pass];
+ }
+ } else {
+ ++row;
+ }
+ }
+ }
+ }
+
+ context.current_frame = i;
+ context.state = GIFLoadingContext::State::FrameComplete;
+ }
+
+ return true;
+}
+
+static bool load_gif_frame_descriptors(GIFLoadingContext& context)
+{
+ if (context.data_size < 32)
+ return false;
+
+ InputMemoryStream stream { { context.data, context.data_size } };
+
+ Optional<GIFFormat> format = decode_gif_header(stream);
+ if (!format.has_value()) {
+ return false;
+ }
+
+ LittleEndian<u16> value;
+
+ stream >> value;
+ context.logical_screen.width = value;
+
+ stream >> value;
+ context.logical_screen.height = value;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (context.logical_screen.width > maximum_width_for_decoded_images || context.logical_screen.height > maximum_height_for_decoded_images) {
+ dbgln("This GIF is too large for comfort: {}x{}", context.logical_screen.width, context.logical_screen.height);
+ return false;
+ }
+
+ u8 gcm_info = 0;
+ stream >> gcm_info;
+
+ if (stream.handle_any_error())
+ return false;
+
+ stream >> context.background_color_index;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 pixel_aspect_ratio = 0;
+ stream >> pixel_aspect_ratio;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 bits_per_pixel = (gcm_info & 7) + 1;
+ int color_map_entry_count = 1;
+ for (int i = 0; i < bits_per_pixel; ++i)
+ color_map_entry_count *= 2;
+
+ for (int i = 0; i < color_map_entry_count; ++i) {
+ u8 r = 0;
+ u8 g = 0;
+ u8 b = 0;
+ stream >> r >> g >> b;
+ context.logical_screen.color_map[i] = { r, g, b };
+ }
+
+ if (stream.handle_any_error())
+ return false;
+
+ NonnullOwnPtr<ImageDescriptor> current_image = make<ImageDescriptor>();
+ for (;;) {
+ u8 sentinel = 0;
+ stream >> sentinel;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (sentinel == '!') {
+ u8 extension_type = 0;
+ stream >> extension_type;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 sub_block_length = 0;
+
+ Vector<u8> sub_block {};
+ for (;;) {
+ stream >> sub_block_length;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (sub_block_length == 0)
+ break;
+
+ u8 dummy = 0;
+ for (u16 i = 0; i < sub_block_length; ++i) {
+ stream >> dummy;
+ sub_block.append(dummy);
+ }
+
+ if (stream.handle_any_error())
+ return false;
+ }
+
+ if (extension_type == 0xF9) {
+ if (sub_block.size() != 4) {
+#ifdef GIF_DEBUG
+ dbgln("Unexpected graphic control size");
+#endif
+ continue;
+ }
+
+ u8 disposal_method = (sub_block[0] & 0x1C) >> 2;
+ current_image->disposal_method = (ImageDescriptor::DisposalMethod)disposal_method;
+
+ u8 user_input = (sub_block[0] & 0x2) >> 1;
+ current_image->user_input = user_input == 1;
+
+ u8 transparent = sub_block[0] & 1;
+ current_image->transparent = transparent == 1;
+
+ u16 duration = sub_block[1] + ((u16)sub_block[2] >> 8);
+ current_image->duration = duration;
+
+ current_image->transparency_index = sub_block[3];
+ }
+
+ if (extension_type == 0xFF) {
+ if (sub_block.size() != 14) {
+#ifdef GIF_DEBUG
+ dbg() << "Unexpected application extension size: " << sub_block.size();
+#endif
+ continue;
+ }
+
+ if (sub_block[11] != 1) {
+#ifdef GIF_DEBUG
+ dbgln("Unexpected application extension format");
+#endif
+ continue;
+ }
+
+ u16 loops = sub_block[12] + (sub_block[13] << 8);
+ context.loops = loops;
+ }
+
+ continue;
+ }
+
+ if (sentinel == ',') {
+ context.images.append(move(current_image));
+ auto& image = context.images.last();
+
+ LittleEndian<u16> tmp;
+
+ u8 packed_fields { 0 };
+
+ stream >> tmp;
+ image.x = tmp;
+
+ stream >> tmp;
+ image.y = tmp;
+
+ stream >> tmp;
+ image.width = tmp;
+
+ stream >> tmp;
+ image.height = tmp;
+
+ stream >> packed_fields;
+ if (stream.handle_any_error())
+ return false;
+
+ image.use_global_color_map = !(packed_fields & 0x80);
+ image.interlaced = (packed_fields & 0x40) != 0;
+
+ if (!image.use_global_color_map) {
+ size_t local_color_table_size = pow(2, (packed_fields & 7) + 1);
+
+ for (size_t i = 0; i < local_color_table_size; ++i) {
+ u8 r = 0;
+ u8 g = 0;
+ u8 b = 0;
+ stream >> r >> g >> b;
+ image.color_map[i] = { r, g, b };
+ }
+ }
+
+ stream >> image.lzw_min_code_size;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 lzw_encoded_bytes_expected = 0;
+
+ for (;;) {
+ stream >> lzw_encoded_bytes_expected;
+
+ if (stream.handle_any_error())
+ return false;
+
+ if (lzw_encoded_bytes_expected == 0)
+ break;
+
+ Array<u8, 256> buffer;
+ stream >> buffer.span().trim(lzw_encoded_bytes_expected);
+
+ if (stream.handle_any_error())
+ return false;
+
+ for (int i = 0; i < lzw_encoded_bytes_expected; ++i) {
+ image.lzw_encoded_bytes.append(buffer[i]);
+ }
+ }
+
+ current_image = make<ImageDescriptor>();
+ continue;
+ }
+
+ if (sentinel == ';') {
+ break;
+ }
+
+ return false;
+ }
+
+ context.state = GIFLoadingContext::State::FrameDescriptorsLoaded;
+ return true;
+}
+
+GIFImageDecoderPlugin::GIFImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<GIFLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+GIFImageDecoderPlugin::~GIFImageDecoderPlugin() { }
+
+IntSize GIFImageDecoderPlugin::size()
+{
+ if (m_context->error_state == GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors) {
+ return {};
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return {};
+ }
+ }
+
+ return { m_context->logical_screen.width, m_context->logical_screen.height };
+}
+
+RefPtr<Gfx::Bitmap> GIFImageDecoderPlugin::bitmap()
+{
+ if (m_context->state < GIFLoadingContext::State::FrameComplete) {
+ return frame(0).image;
+ }
+ return m_context->frame_buffer;
+}
+
+void GIFImageDecoderPlugin::set_volatile()
+{
+ if (m_context->frame_buffer) {
+ m_context->frame_buffer->set_volatile();
+ }
+}
+
+bool GIFImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->frame_buffer) {
+ return true;
+ }
+ return m_context->frame_buffer->set_nonvolatile();
+}
+
+bool GIFImageDecoderPlugin::sniff()
+{
+ InputMemoryStream stream { { m_context->data, m_context->data_size } };
+ return decode_gif_header(stream).has_value();
+}
+
+bool GIFImageDecoderPlugin::is_animated()
+{
+ if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) {
+ return false;
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return false;
+ }
+ }
+
+ return m_context->images.size() > 1;
+}
+
+size_t GIFImageDecoderPlugin::loop_count()
+{
+ if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) {
+ return 0;
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return 0;
+ }
+ }
+
+ return m_context->loops;
+}
+
+size_t GIFImageDecoderPlugin::frame_count()
+{
+ if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) {
+ return 1;
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return 1;
+ }
+ }
+
+ return m_context->images.size();
+}
+
+ImageFrameDescriptor GIFImageDecoderPlugin::frame(size_t i)
+{
+ if (m_context->error_state >= GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
+ return {};
+ }
+
+ if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
+ if (!load_gif_frame_descriptors(*m_context)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
+ return {};
+ }
+ }
+
+ if (m_context->error_state == GIFLoadingContext::ErrorState::NoError && !decode_frame(*m_context, i)) {
+ if (m_context->state < GIFLoadingContext::State::FrameComplete || !decode_frame(*m_context, 0)) {
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
+ return {};
+ }
+ m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
+ }
+
+ ImageFrameDescriptor frame {};
+ frame.image = m_context->frame_buffer->clone();
+ frame.duration = m_context->images.at(i).duration * 10;
+
+ if (frame.duration <= 10) {
+ frame.duration = 100;
+ }
+
+ return frame;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/GIFLoader.h b/Userland/Libraries/LibGfx/GIFLoader.h
new file mode 100644
index 0000000000..24be735381
--- /dev/null
+++ b/Userland/Libraries/LibGfx/GIFLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_gif(const StringView& path);
+RefPtr<Gfx::Bitmap> load_gif_from_memory(const u8*, size_t);
+
+struct GIFLoadingContext;
+
+class GIFImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~GIFImageDecoderPlugin() override;
+ GIFImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<GIFLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Gamma.h b/Userland/Libraries/LibGfx/Gamma.h
new file mode 100644
index 0000000000..f6be93b8fe
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Gamma.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Color.h"
+#include <math.h>
+#include <xmmintrin.h>
+
+#include <AK/SIMD.h>
+
+#define GAMMA 2.2
+
+// Most computer graphics are stored in the sRGB color space, which stores something close to
+// the square root of the display intensity of each color channel. This is problematic for most
+// operations that we want to perform on colors, since they typically assume that color scales
+// linearly (e.g. rgb(127, 0, 0) is half as bright as rgb(255, 0, 0)). This causes incorrect
+// results that look more gray than they should, to fix this we have to convert colors to the linear
+// color space before performing these operations, then convert back before displaying.
+//
+// Conversion between linear and sRGB spaces are somewhat expensive to do on the CPU, so we instead
+// interpret sRGB colors as gamma2.2 colors, which are close enough in most cases to be indistinguishable.
+// Gamma 2.2 colors follow the simple rule of `display_intensity = pow(stored_intensity, 2.2)`.
+// This module implements some fast color space transforms between the gamma2.2 and linear color spaces, plus
+// some common primitive operations like blending.
+//
+// For a more in-depth overview of how gamma-adjustment works, check out:
+// https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
+
+namespace Gfx {
+
+using AK::SIMD::f32x4;
+
+#ifndef NO_FPU
+
+# ifdef __SSE__
+
+// Transform f32x4 from gamma2.2 space to linear space
+// Assumes x is in range [0, 1]
+// FIXME: Remove this hack once clang-11 is available as the default in Github Actions.
+// This is apparently sometime mid-December. https://github.com/actions/virtual-environments/issues/2130
+# if !defined(__clang__) || __clang_major__ >= 11
+constexpr f32x4 gamma_to_linear4(f32x4 x)
+# else
+inline f32x4 gamma_to_linear4(f32x4 x)
+# endif
+{
+ return (0.8f + 0.2f * x) * x * x;
+}
+
+// Transform f32x4 from linear space to gamma2.2 space
+// Assumes x is in range [0, 1]
+inline f32x4 linear_to_gamma4(f32x4 x)
+{
+ // Source for approximation: https://mimosa-pudica.net/fast-gamma/
+ constexpr float a = 0.00279491f;
+ constexpr float b = 1.15907984f;
+ float c = (b / sqrt(1 + a)) - 1;
+ return ((b * __builtin_ia32_rsqrtps(x + a)) - c) * x;
+}
+
+// Linearize v1 and v2, lerp them by mix factor, then convert back.
+// The output is entirely v1 when mix = 0 and entirely v2 when mix = 1
+inline f32x4 gamma_accurate_lerp4(f32x4 v1, f32x4 v2, float mix)
+{
+ return linear_to_gamma4(gamma_to_linear4(v1) * (1 - mix) + gamma_to_linear4(v2) * mix);
+}
+
+# endif
+
+// Transform scalar from gamma2.2 space to linear space
+// Assumes x is in range [0, 1]
+constexpr float gamma_to_linear(float x)
+{
+ return (0.8 + 0.2 * x) * x * x;
+}
+
+// Transform scalar from linear space to gamma2.2 space
+// Assumes x is in range [0, 1]
+inline float linear_to_gamma(float x)
+{
+ // Source for approximation: https://mimosa-pudica.net/fast-gamma/
+ constexpr float a = 0.00279491;
+ constexpr float b = 1.15907984;
+ float c = (b / sqrt(1 + a)) - 1;
+ return ((b / __builtin_sqrt(x + a)) - c) * x;
+}
+
+// Linearize v1 and v2, lerp them by mix factor, then convert back.
+// The output is entirely v1 when mix = 0 and entirely v2 when mix = 1
+inline float gamma_accurate_lerp(float v1, float v2, float mix)
+{
+ return linear_to_gamma(gamma_to_linear(v1) * (1 - mix) + gamma_to_linear(v2) * mix);
+}
+
+// Convert a and b to linear space, blend them by mix factor, then convert back.
+// The output is entirely a when mix = 0 and entirely b when mix = 1
+inline Color gamma_accurate_blend(Color a, Color b, float mix)
+{
+# ifdef __SSE__
+ f32x4 ac = {
+ (float)a.red(),
+ (float)a.green(),
+ (float)a.blue(),
+ };
+ f32x4 bc = {
+ (float)b.red(),
+ (float)b.green(),
+ (float)b.blue(),
+ };
+ f32x4 out = 255.f * gamma_accurate_lerp4(ac * (1.f / 255.f), bc * (1.f / 255.f), mix);
+ return Color(out[0], out[1], out[2]);
+# else
+ return {
+ static_cast<u8>(255. * gamma_accurate_lerp(a.red() / 255., b.red() / 255., mix)),
+ static_cast<u8>(255. * gamma_accurate_lerp(a.green() / 255., b.green() / 255., mix)),
+ static_cast<u8>(255. * gamma_accurate_lerp(a.blue() / 255., b.blue() / 255., mix)),
+ };
+# endif
+}
+
+#endif
+
+}
diff --git a/Userland/Libraries/LibGfx/ICOLoader.cpp b/Userland/Libraries/LibGfx/ICOLoader.cpp
new file mode 100644
index 0000000000..031f4e657e
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ICOLoader.cpp
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2020, Paul Roukema <roukemap@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/ByteBuffer.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/MemoryStream.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/Types.h>
+#include <LibGfx/ICOLoader.h>
+#include <LibGfx/PNGLoader.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+//#define ICO_DEBUG
+
+namespace Gfx {
+
+// FIXME: This is in little-endian order. Maybe need a NetworkOrdered<T> equivalent eventually.
+struct ICONDIR {
+ u16 must_be_0 = 0;
+ u16 must_be_1 = 0;
+ u16 image_count = 0;
+};
+static_assert(sizeof(ICONDIR) == 6);
+
+struct ICONDIRENTRY {
+ u8 width;
+ u8 height;
+ u8 color_count;
+ u8 reserved_0;
+ u16 planes;
+ u16 bits_per_pixel;
+ u32 size;
+ u32 offset;
+};
+static_assert(sizeof(ICONDIRENTRY) == 16);
+
+struct [[gnu::packed]] BMPFILEHEADER {
+ u8 signature[2];
+ u32 size;
+ u16 reserved1;
+ u16 reserved2;
+ u32 offset;
+};
+static_assert(sizeof(BMPFILEHEADER) == 14);
+
+struct BITMAPINFOHEADER {
+ u32 size;
+ i32 width;
+ i32 height;
+ u16 planes;
+ u16 bpp;
+ u32 compression;
+ u32 size_image;
+ u32 vres;
+ u32 hres;
+ u32 palette_size;
+ u32 important_colors;
+};
+static_assert(sizeof(BITMAPINFOHEADER) == 40);
+
+struct [[gnu::packed]] BMP_ARGB {
+ u8 b;
+ u8 g;
+ u8 r;
+ u8 a;
+};
+static_assert(sizeof(BMP_ARGB) == 4);
+
+struct ImageDescriptor {
+ u16 width;
+ u16 height;
+ size_t offset;
+ size_t size;
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+struct ICOLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ Error,
+ DirectoryDecoded,
+ BitmapDecoded
+ };
+ State state { NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ Vector<ImageDescriptor> images;
+ size_t largest_index;
+};
+
+RefPtr<Gfx::Bitmap> load_ico(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ ICOImageDecoderPlugin decoder((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ auto bitmap = decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded ICO: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_ico_from_memory(const u8* data, size_t length)
+{
+ ICOImageDecoderPlugin decoder(data, length);
+ auto bitmap = decoder.bitmap();
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded ICO: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+static Optional<size_t> decode_ico_header(InputMemoryStream& stream)
+{
+ ICONDIR header;
+ stream >> Bytes { &header, sizeof(header) };
+ if (stream.handle_any_error())
+ return {};
+
+ if (header.must_be_0 != 0 || header.must_be_1 != 1)
+ return {};
+ return { header.image_count };
+}
+
+static Optional<ImageDescriptor> decode_ico_direntry(InputMemoryStream& stream)
+{
+ ICONDIRENTRY entry;
+ stream >> Bytes { &entry, sizeof(entry) };
+ if (stream.handle_any_error())
+ return {};
+
+ ImageDescriptor desc = { entry.width, entry.height, entry.offset, entry.size, nullptr };
+ if (desc.width == 0)
+ desc.width = 256;
+ if (desc.height == 0)
+ desc.height = 256;
+
+ return { desc };
+}
+
+static size_t find_largest_image(const ICOLoadingContext& context)
+{
+ size_t max_area = 0;
+ size_t index = 0;
+ size_t largest_index = 0;
+ for (const auto& desc : context.images) {
+ if (desc.width * desc.height > max_area) {
+ max_area = desc.width * desc.height;
+ largest_index = index;
+ }
+ ++index;
+ }
+ return largest_index;
+}
+
+static bool load_ico_directory(ICOLoadingContext& context)
+{
+ InputMemoryStream stream { { context.data, context.data_size } };
+
+ auto image_count = decode_ico_header(stream);
+ if (!image_count.has_value() || image_count.value() == 0) {
+ return false;
+ }
+
+ for (size_t i = 0; i < image_count.value(); ++i) {
+ auto maybe_desc = decode_ico_direntry(stream);
+ if (!maybe_desc.has_value()) {
+#ifdef ICO_DEBUG
+ printf("load_ico_directory: error loading entry: %lu\n", i);
+#endif
+ return false;
+ }
+
+ auto& desc = maybe_desc.value();
+ if (desc.offset + desc.size < desc.offset // detect integer overflow
+ || (desc.offset + desc.size) > context.data_size) {
+#ifdef ICO_DEBUG
+ printf("load_ico_directory: offset: %lu size: %lu doesn't fit in ICO size: %lu\n",
+ desc.offset, desc.size, context.data_size);
+#endif
+ return false;
+ }
+#ifdef ICO_DEBUG
+ printf("load_ico_directory: index %zu width: %u height: %u offset: %lu size: %lu\n",
+ i, desc.width, desc.height, desc.offset, desc.size);
+#endif
+ context.images.append(desc);
+ }
+ context.largest_index = find_largest_image(context);
+ context.state = ICOLoadingContext::State::DirectoryDecoded;
+ return true;
+}
+
+static bool load_ico_bmp(ICOLoadingContext& context, ImageDescriptor& desc)
+{
+ BITMAPINFOHEADER info;
+ if (desc.size < sizeof(info))
+ return false;
+
+ memcpy(&info, context.data + desc.offset, sizeof(info));
+ if (info.size != sizeof(info)) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: info size: %u, expected: %lu\n", info.size, sizeof(info));
+#endif
+ return false;
+ }
+
+ if (info.width < 0) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: width %d < 0\n", info.width);
+#endif
+ return false;
+ }
+ bool topdown = false;
+ if (info.height < 0) {
+ topdown = true;
+ info.height = -info.height;
+ }
+
+ if (info.planes != 1) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: planes: %d != 1", info.planes);
+#endif
+ return false;
+ }
+
+ if (info.bpp != 32) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: unsupported bpp: %u\n", info.bpp);
+#endif
+ return false;
+ }
+
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: width: %d height: %d direction: %s bpp: %d size_image: %u\n",
+ info.width, info.height, topdown ? "TopDown" : "BottomUp", info.bpp, info.size_image);
+#endif
+
+ if (info.compression != 0 || info.palette_size != 0 || info.important_colors != 0) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: following fields must be 0: compression: %u palette_size: %u important_colors: %u\n",
+ info.compression, info.palette_size, info.important_colors);
+#endif
+ return false;
+ }
+
+ if (info.width != desc.width || info.height != 2 * desc.height) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: size mismatch: ico %dx%d, bmp %dx%d\n",
+ desc.width, desc.height, info.width, info.height);
+#endif
+ return false;
+ }
+
+ // Mask is 1bpp, and each row must be 4-byte aligned
+ size_t mask_row_len = align_up_to(align_up_to(desc.width, 8) / 8, 4);
+ size_t required_len = desc.height * (desc.width * sizeof(BMP_ARGB) + mask_row_len);
+ size_t available_len = desc.size - sizeof(info);
+ if (required_len > available_len) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bmp: required_len: %lu > available_len: %lu\n",
+ required_len, available_len);
+#endif
+ return false;
+ }
+
+ desc.bitmap = Bitmap::create_purgeable(BitmapFormat::RGBA32, { desc.width, desc.height });
+ if (!desc.bitmap)
+ return false;
+ Bitmap& bitmap = *desc.bitmap;
+ const u8* image_base = context.data + desc.offset + sizeof(info);
+ const BMP_ARGB* data_base = (const BMP_ARGB*)image_base;
+ const u8* mask_base = image_base + desc.width * desc.height * sizeof(BMP_ARGB);
+ for (int y = 0; y < desc.height; y++) {
+ const u8* row_mask = mask_base + mask_row_len * y;
+ const BMP_ARGB* row_data = data_base + desc.width * y;
+ for (int x = 0; x < desc.width; x++) {
+ u8 mask = !!(row_mask[x / 8] & (0x80 >> (x % 8)));
+ BMP_ARGB data = row_data[x];
+ bitmap.set_pixel(x, topdown ? y : desc.height - y - 1,
+ Color(data.r, data.g, data.b, mask ? 0 : data.a));
+ }
+ }
+ return true;
+}
+
+static bool load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index)
+{
+ if (context.state < ICOLoadingContext::State::DirectoryDecoded) {
+ if (!load_ico_directory(context)) {
+ context.state = ICOLoadingContext::State::Error;
+ return false;
+ }
+ context.state = ICOLoadingContext::State::DirectoryDecoded;
+ }
+ size_t real_index = context.largest_index;
+ if (index.has_value())
+ real_index = index.value();
+ if (real_index >= context.images.size()) {
+ return false;
+ }
+
+ ImageDescriptor& desc = context.images[real_index];
+
+ PNGImageDecoderPlugin png_decoder(context.data + desc.offset, desc.size);
+ if (png_decoder.sniff()) {
+ desc.bitmap = png_decoder.bitmap();
+ if (!desc.bitmap) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bitmap: failed to load PNG encoded image index: %lu\n", real_index);
+#endif
+ return false;
+ }
+ return true;
+ } else {
+ if (!load_ico_bmp(context, desc)) {
+#ifdef ICO_DEBUG
+ printf("load_ico_bitmap: failed to load BMP encoded image index: %lu\n", real_index);
+#endif
+ return false;
+ }
+ return true;
+ }
+}
+
+ICOImageDecoderPlugin::ICOImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<ICOLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+ICOImageDecoderPlugin::~ICOImageDecoderPlugin() { }
+
+IntSize ICOImageDecoderPlugin::size()
+{
+ if (m_context->state == ICOLoadingContext::State::Error) {
+ return {};
+ }
+
+ if (m_context->state < ICOLoadingContext::State::DirectoryDecoded) {
+ if (!load_ico_directory(*m_context)) {
+ m_context->state = ICOLoadingContext::State::Error;
+ return {};
+ }
+ m_context->state = ICOLoadingContext::State::DirectoryDecoded;
+ }
+
+ return { m_context->images[m_context->largest_index].width, m_context->images[m_context->largest_index].height };
+}
+
+RefPtr<Gfx::Bitmap> ICOImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == ICOLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < ICOLoadingContext::State::BitmapDecoded) {
+ // NOTE: This forces the chunk decoding to happen.
+ bool success = load_ico_bitmap(*m_context, {});
+ if (!success) {
+ m_context->state = ICOLoadingContext::State::Error;
+ return nullptr;
+ }
+ m_context->state = ICOLoadingContext::State::BitmapDecoded;
+ }
+
+ ASSERT(m_context->images[m_context->largest_index].bitmap);
+ return m_context->images[m_context->largest_index].bitmap;
+}
+
+void ICOImageDecoderPlugin::set_volatile()
+{
+ if (m_context->images[0].bitmap)
+ m_context->images[0].bitmap->set_volatile();
+}
+
+bool ICOImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->images[0].bitmap)
+ return false;
+ return m_context->images[0].bitmap->set_nonvolatile();
+}
+
+bool ICOImageDecoderPlugin::sniff()
+{
+ InputMemoryStream stream { { m_context->data, m_context->data_size } };
+ return decode_ico_header(stream).has_value();
+}
+
+bool ICOImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t ICOImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t ICOImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor ICOImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ICOLoader.h b/Userland/Libraries/LibGfx/ICOLoader.h
new file mode 100644
index 0000000000..12c40b3398
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ICOLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Paul Roukema <roukemap@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_ico(const StringView& path);
+RefPtr<Gfx::Bitmap> load_ico_from_memory(const u8*, size_t);
+
+struct ICOLoadingContext;
+
+class ICOImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~ICOImageDecoderPlugin() override;
+ ICOImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<ICOLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/ImageDecoder.cpp b/Userland/Libraries/LibGfx/ImageDecoder.cpp
new file mode 100644
index 0000000000..21c4a72a59
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ImageDecoder.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/BMPLoader.h>
+#include <LibGfx/GIFLoader.h>
+#include <LibGfx/ICOLoader.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/JPGLoader.h>
+#include <LibGfx/PBMLoader.h>
+#include <LibGfx/PGMLoader.h>
+#include <LibGfx/PNGLoader.h>
+#include <LibGfx/PPMLoader.h>
+
+namespace Gfx {
+
+ImageDecoder::ImageDecoder(const u8* data, size_t size)
+{
+ m_plugin = make<PNGImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<GIFImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<BMPImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<PBMImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<PGMImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<PPMImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<ICOImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = make<JPGImageDecoderPlugin>(data, size);
+ if (m_plugin->sniff())
+ return;
+
+ m_plugin = nullptr;
+}
+
+ImageDecoder::~ImageDecoder()
+{
+}
+
+RefPtr<Gfx::Bitmap> ImageDecoder::bitmap() const
+{
+ if (!m_plugin)
+ return nullptr;
+ return m_plugin->bitmap();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ImageDecoder.h b/Userland/Libraries/LibGfx/ImageDecoder.h
new file mode 100644
index 0000000000..a054f524a9
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ImageDecoder.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/OwnPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+class Bitmap;
+
+static constexpr size_t maximum_width_for_decoded_images = 16384;
+static constexpr size_t maximum_height_for_decoded_images = 16384;
+
+struct ImageFrameDescriptor {
+ RefPtr<Bitmap> image;
+ int duration { 0 };
+};
+
+class ImageDecoderPlugin {
+public:
+ virtual ~ImageDecoderPlugin() { }
+
+ virtual IntSize size() = 0;
+ virtual RefPtr<Gfx::Bitmap> bitmap() = 0;
+
+ virtual void set_volatile() = 0;
+ [[nodiscard]] virtual bool set_nonvolatile() = 0;
+
+ virtual bool sniff() = 0;
+
+ virtual bool is_animated() = 0;
+ virtual size_t loop_count() = 0;
+ virtual size_t frame_count() = 0;
+ virtual ImageFrameDescriptor frame(size_t i) = 0;
+
+protected:
+ ImageDecoderPlugin() { }
+};
+
+class ImageDecoder : public RefCounted<ImageDecoder> {
+public:
+ static NonnullRefPtr<ImageDecoder> create(const u8* data, size_t size) { return adopt(*new ImageDecoder(data, size)); }
+ static NonnullRefPtr<ImageDecoder> create(const ByteBuffer& data) { return adopt(*new ImageDecoder(data.data(), data.size())); }
+ ~ImageDecoder();
+
+ bool is_valid() const { return m_plugin; }
+
+ IntSize size() const { return m_plugin ? m_plugin->size() : IntSize(); }
+ int width() const { return size().width(); }
+ int height() const { return size().height(); }
+ RefPtr<Gfx::Bitmap> bitmap() const;
+ void set_volatile()
+ {
+ if (m_plugin)
+ m_plugin->set_volatile();
+ }
+ [[nodiscard]] bool set_nonvolatile() { return m_plugin ? m_plugin->set_nonvolatile() : false; }
+ bool sniff() const { return m_plugin ? m_plugin->sniff() : false; }
+ bool is_animated() const { return m_plugin ? m_plugin->is_animated() : false; }
+ size_t loop_count() const { return m_plugin ? m_plugin->loop_count() : 0; }
+ size_t frame_count() const { return m_plugin ? m_plugin->frame_count() : 0; }
+ ImageFrameDescriptor frame(size_t i) const { return m_plugin ? m_plugin->frame(i) : ImageFrameDescriptor(); }
+
+private:
+ ImageDecoder(const u8*, size_t);
+
+ mutable OwnPtr<ImageDecoderPlugin> m_plugin;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/JPGLoader.cpp b/Userland/Libraries/LibGfx/JPGLoader.cpp
new file mode 100644
index 0000000000..5b521804f0
--- /dev/null
+++ b/Userland/Libraries/LibGfx/JPGLoader.cpp
@@ -0,0 +1,1420 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Bitmap.h>
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/MemoryStream.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/JPGLoader.h>
+#include <math.h>
+
+//#define JPG_DEBUG
+
+#define JPG_INVALID 0X0000
+
+#define JPG_APPN0 0XFFE0
+#define JPG_APPN1 0XFFE1
+#define JPG_APPN2 0XFFE2
+#define JPG_APPN3 0XFFE3
+#define JPG_APPN4 0XFFE4
+#define JPG_APPN5 0XFFE5
+#define JPG_APPN6 0XFFE6
+#define JPG_APPN7 0XFFE7
+#define JPG_APPN8 0XFFE8
+#define JPG_APPN9 0XFFE9
+#define JPG_APPNA 0XFFEA
+#define JPG_APPNB 0XFFEB
+#define JPG_APPNC 0XFFEC
+#define JPG_APPND 0XFFED
+#define JPG_APPNE 0xFFEE
+#define JPG_APPNF 0xFFEF
+
+#define JPG_RESERVED1 0xFFF1
+#define JPG_RESERVED2 0xFFF2
+#define JPG_RESERVED3 0xFFF3
+#define JPG_RESERVED4 0xFFF4
+#define JPG_RESERVED5 0xFFF5
+#define JPG_RESERVED6 0xFFF6
+#define JPG_RESERVED7 0xFFF7
+#define JPG_RESERVED8 0xFFF8
+#define JPG_RESERVED9 0xFFF9
+#define JPG_RESERVEDA 0xFFFA
+#define JPG_RESERVEDB 0xFFFB
+#define JPG_RESERVEDC 0xFFFC
+#define JPG_RESERVEDD 0xFFFD
+
+#define JPG_RST0 0xFFD0
+#define JPG_RST1 0xFFD1
+#define JPG_RST2 0xFFD2
+#define JPG_RST3 0xFFD3
+#define JPG_RST4 0xFFD4
+#define JPG_RST5 0xFFD5
+#define JPG_RST6 0xFFD6
+#define JPG_RST7 0xFFD7
+
+#define JPG_DHP 0xFFDE
+#define JPG_EXP 0xFFDF
+
+#define JPG_DHT 0XFFC4
+#define JPG_DQT 0XFFDB
+#define JPG_EOI 0xFFD9
+#define JPG_RST 0XFFDD
+#define JPG_SOF0 0XFFC0
+#define JPG_SOF2 0xFFC2
+#define JPG_SOI 0XFFD8
+#define JPG_SOS 0XFFDA
+#define JPG_COM 0xFFFE
+
+namespace Gfx {
+
+constexpr static u8 zigzag_map[64] {
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63
+};
+
+using Marker = u16;
+
+/**
+ * MCU means group of data units that are coded together. A data unit is an 8x8
+ * block of component data. In interleaved scans, number of non-interleaved data
+ * units of a component C is Ch * Cv, where Ch and Cv represent the horizontal &
+ * vertical subsampling factors of the component, respectively. A MacroBlock is
+ * an 8x8 block of RGB values before encoding, and 8x8 block of YCbCr values when
+ * we're done decoding the huffman stream.
+ */
+struct Macroblock {
+ union {
+ i32 y[64] = { 0 };
+ i32 r[64];
+ };
+
+ union {
+ i32 cb[64] = { 0 };
+ i32 g[64];
+ };
+
+ union {
+ i32 cr[64] = { 0 };
+ i32 b[64];
+ };
+};
+
+struct MacroblockMeta {
+ u32 total { 0 };
+ u32 padded_total { 0 };
+ u32 hcount { 0 };
+ u32 vcount { 0 };
+ u32 hpadded_count { 0 };
+ u32 vpadded_count { 0 };
+};
+
+struct ComponentSpec {
+ u8 serial_id { 255 }; // In the interval [0, 3).
+ u8 id { 0 };
+ u8 hsample_factor { 1 }; // Horizontal sampling factor.
+ u8 vsample_factor { 1 }; // Vertical sampling factor.
+ u8 ac_destination_id { 0 };
+ u8 dc_destination_id { 0 };
+ u8 qtable_id { 0 }; // Quantization table id.
+};
+
+struct StartOfFrame {
+
+ // Of these, only the first 3 are in mainstream use, and refers to SOF0-2.
+ enum class FrameType {
+ Baseline_DCT = 0,
+ Extended_Sequential_DCT = 1,
+ Progressive_DCT = 2,
+ Sequential_Lossless = 3,
+ Differential_Sequential_DCT = 5,
+ Differential_Progressive_DCT = 6,
+ Differential_Sequential_Lossless = 7,
+ Extended_Sequential_DCT_Arithmetic = 9,
+ Progressive_DCT_Arithmetic = 10,
+ Sequential_Lossless_Arithmetic = 11,
+ Differential_Sequential_DCT_Arithmetic = 13,
+ Differential_Progressive_DCT_Arithmetic = 14,
+ Differential_Sequential_Lossless_Arithmetic = 15,
+ };
+
+ FrameType type { FrameType::Baseline_DCT };
+ u8 precision { 0 };
+ u16 height { 0 };
+ u16 width { 0 };
+};
+
+struct HuffmanTableSpec {
+ u8 type { 0 };
+ u8 destination_id { 0 };
+ u8 code_counts[16] = { 0 };
+ Vector<u8> symbols;
+ Vector<u16> codes;
+};
+
+struct HuffmanStreamState {
+ Vector<u8> stream;
+ u8 bit_offset { 0 };
+ size_t byte_offset { 0 };
+};
+
+struct JPGLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ Error,
+ FrameDecoded,
+ BitmapDecoded
+ };
+
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ u32 luma_table[64] = { 0 };
+ u32 chroma_table[64] = { 0 };
+ StartOfFrame frame;
+ u8 hsample_factor { 0 };
+ u8 vsample_factor { 0 };
+ u8 component_count { 0 };
+ HashMap<u8, ComponentSpec> components;
+ RefPtr<Gfx::Bitmap> bitmap;
+ u16 dc_reset_interval { 0 };
+ HashMap<u8, HuffmanTableSpec> dc_tables;
+ HashMap<u8, HuffmanTableSpec> ac_tables;
+ HuffmanStreamState huffman_stream;
+ i32 previous_dc_values[3] = { 0 };
+ MacroblockMeta mblock_meta;
+};
+
+static void generate_huffman_codes(HuffmanTableSpec& table)
+{
+ unsigned code = 0;
+ for (auto number_of_codes : table.code_counts) {
+ for (int i = 0; i < number_of_codes; i++)
+ table.codes.append(code++);
+ code <<= 1;
+ }
+}
+
+static Optional<size_t> read_huffman_bits(HuffmanStreamState& hstream, size_t count = 1)
+{
+ if (count > (8 * sizeof(size_t))) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("Can't read %zu bits at once!", count);
+#endif
+ return {};
+ }
+ size_t value = 0;
+ while (count--) {
+ if (hstream.byte_offset >= hstream.stream.size()) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("Huffman stream exhausted. This could be an error!");
+#endif
+ return {};
+ }
+ u8 current_byte = hstream.stream[hstream.byte_offset];
+ u8 current_bit = 1u & (u32)(current_byte >> (7 - hstream.bit_offset)); // MSB first.
+ hstream.bit_offset++;
+ value = (value << 1) | (size_t)current_bit;
+ if (hstream.bit_offset == 8) {
+ hstream.byte_offset++;
+ hstream.bit_offset = 0;
+ }
+ }
+ return value;
+}
+
+static Optional<u8> get_next_symbol(HuffmanStreamState& hstream, const HuffmanTableSpec& table)
+{
+ unsigned code = 0;
+ size_t code_cursor = 0;
+ for (int i = 0; i < 16; i++) { // Codes can't be longer than 16 bits.
+ auto result = read_huffman_bits(hstream);
+ if (!result.has_value())
+ return {};
+ code = (code << 1) | (i32)result.release_value();
+ for (int j = 0; j < table.code_counts[i]; j++) {
+ if (code == table.codes[code_cursor])
+ return table.symbols[code_cursor];
+ code_cursor++;
+ }
+ }
+
+#ifdef JPG_DEBUG
+ dbgln("If you're seeing this...the jpeg decoder needs to support more kinds of JPEGs!");
+#endif
+ return {};
+}
+
+/**
+ * Build the macroblocks possible by reading single (MCU) subsampled pair of CbCr.
+ * Depending on the sampling factors, we may not see triples of y, cb, cr in that
+ * order. If sample factors differ from one, we'll read more than one block of y-
+ * coefficients before we get to read a cb-cr block.
+
+ * In the function below, `hcursor` and `vcursor` denote the location of the block
+ * we're building in the macroblock matrix. `vfactor_i` and `hfactor_i` are cursors
+ * that iterate over the vertical and horizontal subsampling factors, respectively.
+ * When we finish one iteration of the innermost loop, we'll have the coefficients
+ * of one of the components of block at position `mb_index`. When the outermost loop
+ * finishes first iteration, we'll have all the luminance coefficients for all the
+ * macroblocks that share the chrominance data. Next two iterations (assuming that
+ * we are dealing with three components) will fill up the blocks with chroma data.
+ */
+static bool build_macroblocks(JPGLoadingContext& context, Vector<Macroblock>& macroblocks, u8 hcursor, u8 vcursor)
+{
+ for (auto it = context.components.begin(); it != context.components.end(); ++it) {
+ ComponentSpec& component = it->value;
+
+ if (component.dc_destination_id >= context.dc_tables.size())
+ return false;
+ if (component.ac_destination_id >= context.ac_tables.size())
+ return false;
+
+ for (u8 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) {
+ for (u8 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor);
+ Macroblock& block = macroblocks[mb_index];
+
+ auto& dc_table = context.dc_tables.find(component.dc_destination_id)->value;
+ auto& ac_table = context.ac_tables.find(component.ac_destination_id)->value;
+
+ auto symbol_or_error = get_next_symbol(context.huffman_stream, dc_table);
+ if (!symbol_or_error.has_value())
+ return false;
+
+ // For DC coefficients, symbol encodes the length of the coefficient.
+ auto dc_length = symbol_or_error.release_value();
+ if (dc_length > 11) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("DC coefficient too long: %i!", dc_length);
+#endif
+ return false;
+ }
+
+ auto coeff_or_error = read_huffman_bits(context.huffman_stream, dc_length);
+ if (!coeff_or_error.has_value())
+ return false;
+
+ // DC coefficients are encoded as the difference between previous and current DC values.
+ i32 dc_diff = coeff_or_error.release_value();
+
+ // If MSB in diff is 0, the difference is -ve. Otherwise +ve.
+ if (dc_length != 0 && dc_diff < (1 << (dc_length - 1)))
+ dc_diff -= (1 << dc_length) - 1;
+
+ i32* select_component = component.serial_id == 0 ? block.y : (component.serial_id == 1 ? block.cb : block.cr);
+ auto& previous_dc = context.previous_dc_values[component.serial_id];
+ select_component[0] = previous_dc += dc_diff;
+
+ // Compute the AC coefficients.
+ for (int j = 1; j < 64;) {
+ symbol_or_error = get_next_symbol(context.huffman_stream, ac_table);
+ if (!symbol_or_error.has_value())
+ return false;
+
+ // AC symbols encode 2 pieces of information, the high 4 bits represent
+ // number of zeroes to be stuffed before reading the coefficient. Low 4
+ // bits represent the magnitude of the coefficient.
+ auto ac_symbol = symbol_or_error.release_value();
+ if (ac_symbol == 0)
+ break;
+
+ // ac_symbol = 0xF0 means we need to skip 16 zeroes.
+ u8 run_length = ac_symbol == 0xF0 ? 16 : ac_symbol >> 4;
+ j += run_length;
+
+ if (j >= 64) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("Run-length exceeded boundaries. Cursor: %i, Skipping: %i!", j, run_length);
+#endif
+ return false;
+ }
+
+ u8 coeff_length = ac_symbol & 0x0F;
+ if (coeff_length > 10) {
+#ifdef JPG_DEBUG
+ dbg() << String::format("AC coefficient too long: %i!", coeff_length);
+#endif
+ return false;
+ }
+
+ if (coeff_length != 0) {
+ coeff_or_error = read_huffman_bits(context.huffman_stream, coeff_length);
+ if (!coeff_or_error.has_value())
+ return false;
+ i32 ac_coefficient = coeff_or_error.release_value();
+ if (ac_coefficient < (1 << (coeff_length - 1)))
+ ac_coefficient -= (1 << coeff_length) - 1;
+
+ select_component[zigzag_map[j++]] = ac_coefficient;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+static Optional<Vector<Macroblock>> decode_huffman_stream(JPGLoadingContext& context)
+{
+ Vector<Macroblock> macroblocks;
+ macroblocks.resize(context.mblock_meta.padded_total);
+
+#ifdef JPG_DEBUG
+ dbg() << "Image width: " << context.frame.width;
+ dbg() << "Image height: " << context.frame.height;
+ dbg() << "Macroblocks in a row: " << context.mblock_meta.hpadded_count;
+ dbg() << "Macroblocks in a column: " << context.mblock_meta.vpadded_count;
+ dbg() << "Macroblock meta padded total: " << context.mblock_meta.padded_total;
+#endif
+
+ // Compute huffman codes for DC and AC tables.
+ for (auto it = context.dc_tables.begin(); it != context.dc_tables.end(); ++it)
+ generate_huffman_codes(it->value);
+
+ for (auto it = context.ac_tables.begin(); it != context.ac_tables.end(); ++it)
+ generate_huffman_codes(it->value);
+
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ u32 i = vcursor * context.mblock_meta.hpadded_count + hcursor;
+ if (context.dc_reset_interval > 0) {
+ if (i % context.dc_reset_interval == 0) {
+ context.previous_dc_values[0] = 0;
+ context.previous_dc_values[1] = 0;
+ context.previous_dc_values[2] = 0;
+
+ // Restart markers are stored in byte boundaries. Advance the huffman stream cursor to
+ // the 0th bit of the next byte.
+ if (context.huffman_stream.byte_offset < context.huffman_stream.stream.size()) {
+ if (context.huffman_stream.bit_offset > 0) {
+ context.huffman_stream.bit_offset = 0;
+ context.huffman_stream.byte_offset++;
+ }
+
+ // Skip the restart marker (RSTn).
+ context.huffman_stream.byte_offset++;
+ }
+ }
+ }
+
+ if (!build_macroblocks(context, macroblocks, hcursor, vcursor)) {
+#ifdef JPG_DEBUG
+ dbg() << "Failed to build Macroblock " << i;
+ dbg() << "Huffman stream byte offset " << context.huffman_stream.byte_offset;
+ dbg() << "Huffman stream bit offset " << context.huffman_stream.bit_offset;
+#endif
+ return {};
+ }
+ }
+ }
+
+ return macroblocks;
+}
+
+static inline bool bounds_okay(const size_t cursor, const size_t delta, const size_t bound)
+{
+ return (delta + cursor) < bound;
+}
+
+static inline bool is_valid_marker(const Marker marker)
+{
+ if (marker >= JPG_APPN0 && marker <= JPG_APPNF) {
+#ifdef JPG_DEBUG
+ if (marker != JPG_APPN0)
+ dbg() << String::format("%04x not supported yet. The decoder may fail!", marker);
+#endif
+ return true;
+ }
+ if (marker >= JPG_RESERVED1 && marker <= JPG_RESERVEDD)
+ return true;
+ if (marker >= JPG_RST0 && marker <= JPG_RST7)
+ return true;
+ switch (marker) {
+ case JPG_COM:
+ case JPG_DHP:
+ case JPG_EXP:
+ case JPG_DHT:
+ case JPG_DQT:
+ case JPG_RST:
+ case JPG_SOF0:
+ case JPG_SOI:
+ case JPG_SOS:
+ return true;
+ }
+
+ if (marker >= 0xFFC0 && marker <= 0xFFCF) {
+ if (marker != 0xFFC4 && marker != 0xFFC8 && marker != 0xFFCC) {
+#ifdef JPG_DEBUG
+ dbg() << "Decoding this frame-type (SOF" << (marker & 0xf) << ") is not currently supported. Decoder will fail!";
+#endif
+ return false;
+ }
+ }
+
+ return false;
+}
+
+static inline u16 read_be_word(InputMemoryStream& stream)
+{
+ BigEndian<u16> tmp;
+ stream >> tmp;
+ return tmp;
+}
+
+static inline Marker read_marker_at_cursor(InputMemoryStream& stream)
+{
+ u16 marker = read_be_word(stream);
+ if (stream.handle_any_error())
+ return JPG_INVALID;
+ if (is_valid_marker(marker))
+ return marker;
+ if (marker != 0xFFFF)
+ return JPG_INVALID;
+ u8 next;
+ do {
+ stream >> next;
+ if (stream.handle_any_error() || next == 0x00)
+ return JPG_INVALID;
+ } while (next == 0xFF);
+ marker = 0xFF00 | (u16)next;
+ return is_valid_marker(marker) ? marker : JPG_INVALID;
+}
+
+static bool read_start_of_scan(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ if (context.state < JPGLoadingContext::State::FrameDecoded) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": SOS found before reading a SOF!";
+#endif
+ return false;
+ }
+
+ u16 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ bytes_to_read -= 2;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+ u8 component_count = 0;
+ stream >> component_count;
+ if (stream.handle_any_error())
+ return false;
+ if (component_count != context.component_count) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset()
+ << String::format(": Unsupported number of components: %i!", component_count);
+#endif
+ return false;
+ }
+
+ for (int i = 0; i < component_count; i++) {
+ ComponentSpec* component = nullptr;
+ u8 component_id = 0;
+ stream >> component_id;
+ if (stream.handle_any_error())
+ return false;
+
+ auto it = context.components.find(component_id);
+ if (it != context.components.end()) {
+ component = &it->value;
+ if (i != component->serial_id) {
+ dbgln("JPEG decode failed (i != component->serial_id)");
+ return false;
+ }
+ } else {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unsupported component id: %i!", component_id);
+#endif
+ return false;
+ }
+
+ u8 table_ids = 0;
+ stream >> table_ids;
+ if (stream.handle_any_error())
+ return false;
+
+ component->dc_destination_id = table_ids >> 4;
+ component->ac_destination_id = table_ids & 0x0F;
+
+ if (context.dc_tables.size() != context.ac_tables.size()) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": DC & AC table count mismatch!";
+#endif
+ return false;
+ }
+
+ if (!context.dc_tables.contains(component->dc_destination_id)) {
+#ifdef JPG_DEBUG
+ dbgln("DC table (id: {}) does not exist!", component->dc_destination_id);
+#endif
+ return false;
+ }
+
+ if (!context.ac_tables.contains(component->ac_destination_id)) {
+#ifdef JPG_DEBUG
+ dbgln("AC table (id: {}) does not exist!", component->ac_destination_id);
+#endif
+ return false;
+ }
+ }
+
+ u8 spectral_selection_start = 0;
+ stream >> spectral_selection_start;
+ if (stream.handle_any_error())
+ return false;
+ u8 spectral_selection_end = 0;
+ stream >> spectral_selection_end;
+ if (stream.handle_any_error())
+ return false;
+ u8 successive_approximation = 0;
+ stream >> successive_approximation;
+ if (stream.handle_any_error())
+ return false;
+ // The three values should be fixed for baseline JPEGs utilizing sequential DCT.
+ if (spectral_selection_start != 0 || spectral_selection_end != 63 || successive_approximation != 0) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": ERROR! Start of Selection: " << spectral_selection_start
+ << ", End of Selection: " << spectral_selection_end
+ << ", Successive Approximation: " << successive_approximation << "!";
+#endif
+ return false;
+ }
+ return true;
+}
+
+static bool read_reset_marker(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ u16 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ bytes_to_read -= 2;
+ if (bytes_to_read != 2) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Malformed reset marker found!";
+#endif
+ return false;
+ }
+ context.dc_reset_interval = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ return true;
+}
+
+static bool read_huffman_table(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ i32 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+ bytes_to_read -= 2;
+ while (bytes_to_read > 0) {
+ HuffmanTableSpec table;
+ u8 table_info = 0;
+ stream >> table_info;
+ if (stream.handle_any_error())
+ return false;
+ u8 table_type = table_info >> 4;
+ u8 table_destination_id = table_info & 0x0F;
+ if (table_type > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unrecognized huffman table: %i!", table_type);
+#endif
+ return false;
+ }
+ if (table_destination_id > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset()
+ << String::format(": Invalid huffman table destination id: %i!", table_destination_id);
+#endif
+ return false;
+ }
+
+ table.type = table_type;
+ table.destination_id = table_destination_id;
+ u32 total_codes = 0;
+
+ // Read code counts. At each index K, the value represents the number of K+1 bit codes in this header.
+ for (int i = 0; i < 16; i++) {
+ u8 count = 0;
+ stream >> count;
+ if (stream.handle_any_error())
+ return false;
+ total_codes += count;
+ table.code_counts[i] = count;
+ }
+
+ table.codes.ensure_capacity(total_codes);
+
+ // Read symbols. Read X bytes, where X is the sum of the counts of codes read in the previous step.
+ for (u32 i = 0; i < total_codes; i++) {
+ u8 symbol = 0;
+ stream >> symbol;
+ if (stream.handle_any_error())
+ return false;
+ table.symbols.append(symbol);
+ }
+
+ if (stream.handle_any_error())
+ return false;
+
+ auto& huffman_table = table.type == 0 ? context.dc_tables : context.ac_tables;
+ huffman_table.set(table.destination_id, table);
+ ASSERT(huffman_table.size() <= 2);
+
+ bytes_to_read -= 1 + 16 + total_codes;
+ }
+
+ if (bytes_to_read != 0) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Extra bytes detected in huffman header!";
+#endif
+ return false;
+ }
+ return true;
+}
+
+static inline bool validate_luma_and_modify_context(const ComponentSpec& luma, JPGLoadingContext& context)
+{
+ if ((luma.hsample_factor == 1 || luma.hsample_factor == 2) && (luma.vsample_factor == 1 || luma.vsample_factor == 2)) {
+ context.mblock_meta.hpadded_count += luma.hsample_factor == 1 ? 0 : context.mblock_meta.hcount % 2;
+ context.mblock_meta.vpadded_count += luma.vsample_factor == 1 ? 0 : context.mblock_meta.vcount % 2;
+ context.mblock_meta.padded_total = context.mblock_meta.hpadded_count * context.mblock_meta.vpadded_count;
+ // For easy reference to relevant sample factors.
+ context.hsample_factor = luma.hsample_factor;
+ context.vsample_factor = luma.vsample_factor;
+#ifdef JPG_DEBUG
+ dbg() << String::format("Horizontal Subsampling Factor: %i", luma.hsample_factor);
+ dbg() << String::format("Vertical Subsampling Factor: %i", luma.vsample_factor);
+#endif
+ return true;
+ }
+ return false;
+}
+
+static inline void set_macroblock_metadata(JPGLoadingContext& context)
+{
+ context.mblock_meta.hcount = (context.frame.width + 7) / 8;
+ context.mblock_meta.vcount = (context.frame.height + 7) / 8;
+ context.mblock_meta.hpadded_count = context.mblock_meta.hcount;
+ context.mblock_meta.vpadded_count = context.mblock_meta.vcount;
+ context.mblock_meta.total = context.mblock_meta.hcount * context.mblock_meta.vcount;
+}
+
+static bool read_start_of_frame(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ if (context.state == JPGLoadingContext::FrameDecoded) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": SOF repeated!";
+#endif
+ return false;
+ }
+
+ i32 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+
+ bytes_to_read -= 2;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+
+ stream >> context.frame.precision;
+ if (stream.handle_any_error())
+ return false;
+ if (context.frame.precision != 8) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": SOF precision != 8!";
+#endif
+ return false;
+ }
+
+ context.frame.height = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ context.frame.width = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ if (!context.frame.width || !context.frame.height) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": ERROR! Image height: " << context.frame.height << ", Image width: "
+ << context.frame.width << "!";
+#endif
+ return false;
+ }
+
+ if (context.frame.width > maximum_width_for_decoded_images || context.frame.height > maximum_height_for_decoded_images) {
+ dbgln("This JPEG is too large for comfort: {}x{}", context.frame.width, context.frame.height);
+ return false;
+ }
+
+ set_macroblock_metadata(context);
+
+ stream >> context.component_count;
+ if (stream.handle_any_error())
+ return false;
+ if (context.component_count != 1 && context.component_count != 3) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported number of components in SOF: "
+ << context.component_count << "!";
+#endif
+ return false;
+ }
+
+ for (u8 i = 0; i < context.component_count; i++) {
+ ComponentSpec component;
+ component.serial_id = i;
+
+ stream >> component.id;
+ if (stream.handle_any_error())
+ return false;
+
+ u8 subsample_factors = 0;
+ stream >> subsample_factors;
+ if (stream.handle_any_error())
+ return false;
+ component.hsample_factor = subsample_factors >> 4;
+ component.vsample_factor = subsample_factors & 0x0F;
+
+ if (component.serial_id == 0) {
+ // By convention, downsampling is applied only on chroma components. So we should
+ // hope to see the maximum sampling factor in the luma component.
+ if (!validate_luma_and_modify_context(component, context)) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported luma subsampling factors: "
+ << "horizontal: " << component.hsample_factor << ", vertical: " << component.vsample_factor;
+#endif
+ return false;
+ }
+ } else {
+ if (component.hsample_factor != 1 || component.vsample_factor != 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported chroma subsampling factors: "
+ << "horizontal: " << component.hsample_factor << ", vertical: " << component.vsample_factor;
+#endif
+ return false;
+ }
+ }
+
+ stream >> component.qtable_id;
+ if (stream.handle_any_error())
+ return false;
+ if (component.qtable_id > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Unsupported quantization table id: "
+ << component.qtable_id << "!";
+#endif
+ return false;
+ }
+
+ context.components.set(component.id, component);
+ }
+
+ return true;
+}
+
+static bool read_quantization_table(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ i32 bytes_to_read = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ bytes_to_read -= 2;
+ if (!bounds_okay(stream.offset(), bytes_to_read, context.data_size))
+ return false;
+ while (bytes_to_read > 0) {
+ u8 info_byte = 0;
+ stream >> info_byte;
+ if (stream.handle_any_error())
+ return false;
+ u8 element_unit_hint = info_byte >> 4;
+ if (element_unit_hint > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset()
+ << String::format(": Unsupported unit hint in quantization table: %i!", element_unit_hint);
+#endif
+ return false;
+ }
+ u8 table_id = info_byte & 0x0F;
+ if (table_id > 1) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unsupported quantization table id: %i!", table_id);
+#endif
+ return false;
+ }
+ u32* table = table_id == 0 ? context.luma_table : context.chroma_table;
+ for (int i = 0; i < 64; i++) {
+ if (element_unit_hint == 0) {
+ u8 tmp = 0;
+ stream >> tmp;
+ if (stream.handle_any_error())
+ return false;
+ table[zigzag_map[i]] = tmp;
+ } else {
+ table[zigzag_map[i]] = read_be_word(stream);
+ if (stream.handle_any_error())
+ return false;
+ }
+ }
+ if (stream.handle_any_error())
+ return false;
+
+ bytes_to_read -= 1 + (element_unit_hint == 0 ? 64 : 128);
+ }
+ if (bytes_to_read != 0) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Invalid length for one or more quantization tables!";
+#endif
+ return false;
+ }
+
+ return true;
+}
+
+static bool skip_marker_with_length(InputMemoryStream& stream)
+{
+ u16 bytes_to_skip = read_be_word(stream);
+ bytes_to_skip -= 2;
+ if (stream.handle_any_error())
+ return false;
+ stream.discard_or_error(bytes_to_skip);
+ return !stream.handle_any_error();
+}
+
+static void dequantize(JPGLoadingContext& context, Vector<Macroblock>& macroblocks)
+{
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ for (auto it = context.components.begin(); it != context.components.end(); ++it) {
+ auto& component = it->value;
+ const u32* table = component.qtable_id == 0 ? context.luma_table : context.chroma_table;
+ for (u32 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) {
+ for (u32 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor);
+ Macroblock& block = macroblocks[mb_index];
+ int* block_component = component.serial_id == 0 ? block.y : (component.serial_id == 1 ? block.cb : block.cr);
+ for (u32 k = 0; k < 64; k++)
+ block_component[k] *= table[k];
+ }
+ }
+ }
+ }
+ }
+}
+
+static void inverse_dct(const JPGLoadingContext& context, Vector<Macroblock>& macroblocks)
+{
+ static const float m0 = 2.0 * cos(1.0 / 16.0 * 2.0 * M_PI);
+ static const float m1 = 2.0 * cos(2.0 / 16.0 * 2.0 * M_PI);
+ static const float m3 = 2.0 * cos(2.0 / 16.0 * 2.0 * M_PI);
+ static const float m5 = 2.0 * cos(3.0 / 16.0 * 2.0 * M_PI);
+ static const float m2 = m0 - m5;
+ static const float m4 = m0 + m5;
+ static const float s0 = cos(0.0 / 16.0 * M_PI) / sqrt(8);
+ static const float s1 = cos(1.0 / 16.0 * M_PI) / 2.0;
+ static const float s2 = cos(2.0 / 16.0 * M_PI) / 2.0;
+ static const float s3 = cos(3.0 / 16.0 * M_PI) / 2.0;
+ static const float s4 = cos(4.0 / 16.0 * M_PI) / 2.0;
+ static const float s5 = cos(5.0 / 16.0 * M_PI) / 2.0;
+ static const float s6 = cos(6.0 / 16.0 * M_PI) / 2.0;
+ static const float s7 = cos(7.0 / 16.0 * M_PI) / 2.0;
+
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ for (auto it = context.components.begin(); it != context.components.end(); ++it) {
+ auto& component = it->value;
+ for (u8 vfactor_i = 0; vfactor_i < component.vsample_factor; vfactor_i++) {
+ for (u8 hfactor_i = 0; hfactor_i < component.hsample_factor; hfactor_i++) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hfactor_i + hcursor);
+ Macroblock& block = macroblocks[mb_index];
+ i32* block_component = component.serial_id == 0 ? block.y : (component.serial_id == 1 ? block.cb : block.cr);
+ for (u32 k = 0; k < 8; ++k) {
+ const float g0 = block_component[0 * 8 + k] * s0;
+ const float g1 = block_component[4 * 8 + k] * s4;
+ const float g2 = block_component[2 * 8 + k] * s2;
+ const float g3 = block_component[6 * 8 + k] * s6;
+ const float g4 = block_component[5 * 8 + k] * s5;
+ const float g5 = block_component[1 * 8 + k] * s1;
+ const float g6 = block_component[7 * 8 + k] * s7;
+ const float g7 = block_component[3 * 8 + k] * s3;
+
+ const float f0 = g0;
+ const float f1 = g1;
+ const float f2 = g2;
+ const float f3 = g3;
+ const float f4 = g4 - g7;
+ const float f5 = g5 + g6;
+ const float f6 = g5 - g6;
+ const float f7 = g4 + g7;
+
+ const float e0 = f0;
+ const float e1 = f1;
+ const float e2 = f2 - f3;
+ const float e3 = f2 + f3;
+ const float e4 = f4;
+ const float e5 = f5 - f7;
+ const float e6 = f6;
+ const float e7 = f5 + f7;
+ const float e8 = f4 + f6;
+
+ const float d0 = e0;
+ const float d1 = e1;
+ const float d2 = e2 * m1;
+ const float d3 = e3;
+ const float d4 = e4 * m2;
+ const float d5 = e5 * m3;
+ const float d6 = e6 * m4;
+ const float d7 = e7;
+ const float d8 = e8 * m5;
+
+ const float c0 = d0 + d1;
+ const float c1 = d0 - d1;
+ const float c2 = d2 - d3;
+ const float c3 = d3;
+ const float c4 = d4 + d8;
+ const float c5 = d5 + d7;
+ const float c6 = d6 - d8;
+ const float c7 = d7;
+ const float c8 = c5 - c6;
+
+ const float b0 = c0 + c3;
+ const float b1 = c1 + c2;
+ const float b2 = c1 - c2;
+ const float b3 = c0 - c3;
+ const float b4 = c4 - c8;
+ const float b5 = c8;
+ const float b6 = c6 - c7;
+ const float b7 = c7;
+
+ block_component[0 * 8 + k] = b0 + b7;
+ block_component[1 * 8 + k] = b1 + b6;
+ block_component[2 * 8 + k] = b2 + b5;
+ block_component[3 * 8 + k] = b3 + b4;
+ block_component[4 * 8 + k] = b3 - b4;
+ block_component[5 * 8 + k] = b2 - b5;
+ block_component[6 * 8 + k] = b1 - b6;
+ block_component[7 * 8 + k] = b0 - b7;
+ }
+ for (u32 l = 0; l < 8; ++l) {
+ const float g0 = block_component[l * 8 + 0] * s0;
+ const float g1 = block_component[l * 8 + 4] * s4;
+ const float g2 = block_component[l * 8 + 2] * s2;
+ const float g3 = block_component[l * 8 + 6] * s6;
+ const float g4 = block_component[l * 8 + 5] * s5;
+ const float g5 = block_component[l * 8 + 1] * s1;
+ const float g6 = block_component[l * 8 + 7] * s7;
+ const float g7 = block_component[l * 8 + 3] * s3;
+
+ const float f0 = g0;
+ const float f1 = g1;
+ const float f2 = g2;
+ const float f3 = g3;
+ const float f4 = g4 - g7;
+ const float f5 = g5 + g6;
+ const float f6 = g5 - g6;
+ const float f7 = g4 + g7;
+
+ const float e0 = f0;
+ const float e1 = f1;
+ const float e2 = f2 - f3;
+ const float e3 = f2 + f3;
+ const float e4 = f4;
+ const float e5 = f5 - f7;
+ const float e6 = f6;
+ const float e7 = f5 + f7;
+ const float e8 = f4 + f6;
+
+ const float d0 = e0;
+ const float d1 = e1;
+ const float d2 = e2 * m1;
+ const float d3 = e3;
+ const float d4 = e4 * m2;
+ const float d5 = e5 * m3;
+ const float d6 = e6 * m4;
+ const float d7 = e7;
+ const float d8 = e8 * m5;
+
+ const float c0 = d0 + d1;
+ const float c1 = d0 - d1;
+ const float c2 = d2 - d3;
+ const float c3 = d3;
+ const float c4 = d4 + d8;
+ const float c5 = d5 + d7;
+ const float c6 = d6 - d8;
+ const float c7 = d7;
+ const float c8 = c5 - c6;
+
+ const float b0 = c0 + c3;
+ const float b1 = c1 + c2;
+ const float b2 = c1 - c2;
+ const float b3 = c0 - c3;
+ const float b4 = c4 - c8;
+ const float b5 = c8;
+ const float b6 = c6 - c7;
+ const float b7 = c7;
+
+ block_component[l * 8 + 0] = b0 + b7;
+ block_component[l * 8 + 1] = b1 + b6;
+ block_component[l * 8 + 2] = b2 + b5;
+ block_component[l * 8 + 3] = b3 + b4;
+ block_component[l * 8 + 4] = b3 - b4;
+ block_component[l * 8 + 5] = b2 - b5;
+ block_component[l * 8 + 6] = b1 - b6;
+ block_component[l * 8 + 7] = b0 - b7;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void ycbcr_to_rgb(const JPGLoadingContext& context, Vector<Macroblock>& macroblocks)
+{
+ for (u32 vcursor = 0; vcursor < context.mblock_meta.vcount; vcursor += context.vsample_factor) {
+ for (u32 hcursor = 0; hcursor < context.mblock_meta.hcount; hcursor += context.hsample_factor) {
+ const u32 chroma_block_index = vcursor * context.mblock_meta.hpadded_count + hcursor;
+ const Macroblock& chroma = macroblocks[chroma_block_index];
+ // Overflows are intentional.
+ for (u8 vfactor_i = context.vsample_factor - 1; vfactor_i < context.vsample_factor; --vfactor_i) {
+ for (u8 hfactor_i = context.hsample_factor - 1; hfactor_i < context.hsample_factor; --hfactor_i) {
+ u32 mb_index = (vcursor + vfactor_i) * context.mblock_meta.hpadded_count + (hcursor + hfactor_i);
+ i32* y = macroblocks[mb_index].y;
+ i32* cb = macroblocks[mb_index].cb;
+ i32* cr = macroblocks[mb_index].cr;
+ for (u8 i = 7; i < 8; --i) {
+ for (u8 j = 7; j < 8; --j) {
+ const u8 pixel = i * 8 + j;
+ const u32 chroma_pxrow = (i / context.vsample_factor) + 4 * vfactor_i;
+ const u32 chroma_pxcol = (j / context.hsample_factor) + 4 * hfactor_i;
+ const u32 chroma_pixel = chroma_pxrow * 8 + chroma_pxcol;
+ int r = y[pixel] + 1.402f * chroma.cr[chroma_pixel] + 128;
+ int g = y[pixel] - 0.344f * chroma.cb[chroma_pixel] - 0.714f * chroma.cr[chroma_pixel] + 128;
+ int b = y[pixel] + 1.772f * chroma.cb[chroma_pixel] + 128;
+ y[pixel] = r < 0 ? 0 : (r > 255 ? 255 : r);
+ cb[pixel] = g < 0 ? 0 : (g > 255 ? 255 : g);
+ cr[pixel] = b < 0 ? 0 : (b > 255 ? 255 : b);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static bool compose_bitmap(JPGLoadingContext& context, const Vector<Macroblock>& macroblocks)
+{
+ context.bitmap = Bitmap::create_purgeable(BitmapFormat::RGB32, { context.frame.width, context.frame.height });
+ if (!context.bitmap)
+ return false;
+
+ for (u32 y = context.frame.height - 1; y < context.frame.height; y--) {
+ const u32 block_row = y / 8;
+ const u32 pixel_row = y % 8;
+ for (u32 x = 0; x < context.frame.width; x++) {
+ const u32 block_column = x / 8;
+ auto& block = macroblocks[block_row * context.mblock_meta.hpadded_count + block_column];
+ const u32 pixel_column = x % 8;
+ const u32 pixel_index = pixel_row * 8 + pixel_column;
+ const Color color { (u8)block.y[pixel_index], (u8)block.cb[pixel_index], (u8)block.cr[pixel_index] };
+ context.bitmap->set_pixel(x, y, color);
+ }
+ }
+
+ return true;
+}
+
+static bool parse_header(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ auto marker = read_marker_at_cursor(stream);
+ if (stream.handle_any_error())
+ return false;
+ if (marker != JPG_SOI) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": SOI not found: %x!", marker);
+#endif
+ return false;
+ }
+ for (;;) {
+ marker = read_marker_at_cursor(stream);
+ if (stream.handle_any_error())
+ return false;
+
+ // Set frame type if the marker marks a new frame.
+ if (marker >= 0xFFC0 && marker <= 0xFFCF) {
+ // Ignore interleaved markers.
+ if (marker != 0xFFC4 && marker != 0xFFC8 && marker != 0xFFCC) {
+ context.frame.type = static_cast<StartOfFrame::FrameType>(marker & 0xF);
+ }
+ }
+
+ switch (marker) {
+ case JPG_INVALID:
+ case JPG_RST0:
+ case JPG_RST1:
+ case JPG_RST2:
+ case JPG_RST3:
+ case JPG_RST4:
+ case JPG_RST5:
+ case JPG_RST6:
+ case JPG_RST7:
+ case JPG_SOI:
+ case JPG_EOI:
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Unexpected marker %x!", marker);
+#endif
+ return false;
+ case JPG_SOF0:
+ if (!read_start_of_frame(stream, context))
+ return false;
+ context.state = JPGLoadingContext::FrameDecoded;
+ break;
+ case JPG_DQT:
+ if (!read_quantization_table(stream, context))
+ return false;
+ break;
+ case JPG_RST:
+ if (!read_reset_marker(stream, context))
+ return false;
+ break;
+ case JPG_DHT:
+ if (!read_huffman_table(stream, context))
+ return false;
+ break;
+ case JPG_SOS:
+ return read_start_of_scan(stream, context);
+ default:
+ if (!skip_marker_with_length(stream)) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Error skipping marker: %x!", marker);
+#endif
+ return false;
+ }
+ break;
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+static bool scan_huffman_stream(InputMemoryStream& stream, JPGLoadingContext& context)
+{
+ u8 last_byte;
+ u8 current_byte = 0;
+ stream >> current_byte;
+ if (stream.handle_any_error())
+ return false;
+
+ for (;;) {
+ last_byte = current_byte;
+ stream >> current_byte;
+ if (stream.handle_any_error()) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": EOI not found!";
+#endif
+ return false;
+ }
+
+ if (last_byte == 0xFF) {
+ if (current_byte == 0xFF)
+ continue;
+ if (current_byte == 0x00) {
+ stream >> current_byte;
+ if (stream.handle_any_error())
+ return false;
+ context.huffman_stream.stream.append(last_byte);
+ continue;
+ }
+ Marker marker = 0xFF00 | current_byte;
+ if (marker == JPG_EOI)
+ return true;
+ if (marker >= JPG_RST0 && marker <= JPG_RST7) {
+ context.huffman_stream.stream.append(marker);
+ stream >> current_byte;
+ if (stream.handle_any_error())
+ return false;
+ continue;
+ }
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << String::format(": Invalid marker: %x!", marker);
+#endif
+ return false;
+ } else {
+ context.huffman_stream.stream.append(last_byte);
+ }
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+static bool decode_jpg(JPGLoadingContext& context)
+{
+ InputMemoryStream stream { { context.data, context.data_size } };
+
+ if (!parse_header(stream, context))
+ return false;
+ if (!scan_huffman_stream(stream, context))
+ return false;
+
+ auto result = decode_huffman_stream(context);
+ if (!result.has_value()) {
+#ifdef JPG_DEBUG
+ dbg() << stream.offset() << ": Failed to decode Macroblocks!";
+#endif
+ return false;
+ }
+
+ auto macroblocks = result.release_value();
+ dequantize(context, macroblocks);
+ inverse_dct(context, macroblocks);
+ ycbcr_to_rgb(context, macroblocks);
+ if (!compose_bitmap(context, macroblocks))
+ return false;
+ return true;
+}
+
+static RefPtr<Gfx::Bitmap> load_jpg_impl(const u8* data, size_t data_size)
+{
+ JPGLoadingContext context;
+ context.data = data;
+ context.data_size = data_size;
+
+ if (!decode_jpg(context))
+ return nullptr;
+
+ return context.bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_jpg(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_jpg_impl((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded JPG: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_jpg_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_jpg_impl(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded jpg: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+JPGImageDecoderPlugin::JPGImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<JPGLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+ m_context->huffman_stream.stream.ensure_capacity(50 * KiB);
+}
+
+JPGImageDecoderPlugin::~JPGImageDecoderPlugin()
+{
+}
+
+IntSize JPGImageDecoderPlugin::size()
+{
+ if (m_context->state == JPGLoadingContext::State::Error)
+ return {};
+ if (m_context->state >= JPGLoadingContext::State::FrameDecoded)
+ return { m_context->frame.width, m_context->frame.height };
+
+ return {};
+}
+
+RefPtr<Gfx::Bitmap> JPGImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == JPGLoadingContext::State::Error)
+ return nullptr;
+ if (m_context->state < JPGLoadingContext::State::BitmapDecoded) {
+ if (!decode_jpg(*m_context)) {
+ m_context->state = JPGLoadingContext::State::Error;
+ return nullptr;
+ }
+ m_context->state = JPGLoadingContext::State::BitmapDecoded;
+ }
+
+ return m_context->bitmap;
+}
+
+void JPGImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool JPGImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool JPGImageDecoderPlugin::sniff()
+{
+ return m_context->data_size > 3
+ && m_context->data[0] == 0xFF
+ && m_context->data[1] == 0xD8
+ && m_context->data[2] == 0xFF;
+}
+
+bool JPGImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t JPGImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t JPGImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor JPGImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+ return {};
+}
+}
diff --git a/Userland/Libraries/LibGfx/JPGLoader.h b/Userland/Libraries/LibGfx/JPGLoader.h
new file mode 100644
index 0000000000..67af000882
--- /dev/null
+++ b/Userland/Libraries/LibGfx/JPGLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_jpg(const StringView& path);
+RefPtr<Gfx::Bitmap> load_jpg_from_memory(const u8* data, size_t length);
+
+struct JPGLoadingContext;
+
+class JPGImageDecoderPlugin : public ImageDecoderPlugin {
+public:
+ virtual ~JPGImageDecoderPlugin() override;
+ JPGImageDecoderPlugin(const u8*, size_t);
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<JPGLoadingContext> m_context;
+};
+}
diff --git a/Userland/Libraries/LibGfx/Matrix.h b/Userland/Libraries/LibGfx/Matrix.h
new file mode 100644
index 0000000000..aa9cf5e64f
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Matrix.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <initializer_list>
+
+namespace Gfx {
+
+template<size_t N, typename T>
+class Matrix {
+public:
+ static constexpr size_t Size = N;
+
+ Matrix() = default;
+ Matrix(std::initializer_list<T> elements)
+ {
+ ASSERT(elements.size() == N * N);
+ size_t i = 0;
+ for (auto& element : elements) {
+ m_elements[i / N][i % N] = element;
+ ++i;
+ }
+ }
+
+ template<typename... Args>
+ Matrix(Args... args)
+ : Matrix({ (T)args... })
+ {
+ }
+
+ Matrix(const Matrix& other)
+ {
+ __builtin_memcpy(m_elements, other.elements(), sizeof(T) * N * N);
+ }
+
+ auto elements() const { return m_elements; }
+ auto elements() { return m_elements; }
+
+ Matrix operator*(const Matrix& other) const
+ {
+ Matrix product;
+ for (int i = 0; i < N; ++i) {
+ for (int j = 0; j < N; ++j) {
+ auto& element = product.m_elements[i][j];
+
+ if constexpr (N == 4) {
+ element = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1]
+ + m_elements[2][j] * other.m_elements[i][2]
+ + m_elements[3][j] * other.m_elements[i][3];
+ } else if constexpr (N == 3) {
+ element = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1]
+ + m_elements[2][j] * other.m_elements[i][2];
+ } else if constexpr (N == 2) {
+ element = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1];
+ } else if constexpr (N == 1) {
+ element = m_elements[0][j] * other.m_elements[i][0];
+ } else {
+ T value {};
+ for (size_t k = 0; k < N; ++k)
+ value += m_elements[k][j] * other.m_elements[i][k];
+
+ element = value;
+ }
+ }
+ }
+
+ return product;
+ }
+
+private:
+ T m_elements[N][N];
+};
+
+}
+
+using Gfx::Matrix;
diff --git a/Userland/Libraries/LibGfx/Matrix4x4.h b/Userland/Libraries/LibGfx/Matrix4x4.h
new file mode 100644
index 0000000000..643ab3ea62
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Matrix4x4.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Matrix.h>
+#include <LibGfx/Vector3.h>
+#include <math.h>
+
+namespace Gfx {
+
+template<typename T>
+class Matrix4x4 final : public Matrix<4, T> {
+public:
+ Matrix4x4() = default;
+ Matrix4x4(T _11, T _12, T _13, T _14,
+ T _21, T _22, T _23, T _24,
+ T _31, T _32, T _33, T _34,
+ T _41, T _42, T _43, T _44)
+ : m_elements {
+ _11, _12, _13, _14,
+ _21, _22, _23, _24,
+ _31, _32, _33, _34,
+ _41, _42, _43, _44
+ }
+ {
+ }
+
+ auto elements() const { return m_elements; }
+ auto elements() { return m_elements; }
+
+ Matrix4x4 operator*(const Matrix4x4& other) const
+ {
+ Matrix4x4 product;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ product.m_elements[i][j] = m_elements[0][j] * other.m_elements[i][0]
+ + m_elements[1][j] * other.m_elements[i][1]
+ + m_elements[2][j] * other.m_elements[i][2]
+ + m_elements[3][j] * other.m_elements[i][3];
+ }
+ }
+ return product;
+ }
+
+ Vector3<T> transform_point(const Vector3<T>& p) const
+ {
+ return Vector3<T>(
+ p.x() * m_elements[0][0] + p.y() * m_elements[1][0] + p.z() * m_elements[2][0] + m_elements[3][0],
+ p.x() * m_elements[0][1] + p.y() * m_elements[1][1] + p.z() * m_elements[2][1] + m_elements[3][1],
+ p.x() * m_elements[0][2] + p.y() * m_elements[1][2] + p.z() * m_elements[2][2] + m_elements[3][2]);
+ }
+
+ static Matrix4x4 translate(const Vector3<T>& p)
+ {
+ return Matrix4x4(
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ p.x(), p.y(), p.z(), 1);
+ }
+
+ static Matrix4x4 scale(const Vector3<T>& s)
+ {
+ return Matrix4x4(
+ s.x(), 0, 0, 0,
+ 0, s.y(), 0, 0,
+ 0, 0, s.z(), 0,
+ 0, 0, 0, 1);
+ }
+
+ static Matrix4x4 rotate(const Vector3<T>& axis, T angle)
+ {
+ T c = cos(angle);
+ T s = sin(angle);
+ T t = 1 - c;
+ T x = axis.x();
+ T y = axis.y();
+ T z = axis.z();
+
+ return Matrix4x4(
+ t * x * x + c, t * x * y - z * s, t * x * z + y * s, 0,
+ t * x * y + z * s, t * y * y + c, t * y * z - x * s, 0,
+ t * x * z - y * s, t * y * z + x * s, t * z * z + c, 0,
+ 0, 0, 0, 1);
+ }
+
+private:
+ T m_elements[4][4];
+};
+
+typedef Matrix4x4<float> FloatMatrix4x4;
+typedef Matrix4x4<double> DoubleMatrix4x4;
+
+}
+
+using Gfx::DoubleMatrix4x4;
+using Gfx::FloatMatrix4x4;
+using Gfx::Matrix4x4;
diff --git a/Userland/Libraries/LibGfx/Orientation.h b/Userland/Libraries/LibGfx/Orientation.h
new file mode 100644
index 0000000000..e83af3f530
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Orientation.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+enum class Orientation {
+ Horizontal,
+ Vertical
+};
+
+}
+
+using Gfx::Orientation;
diff --git a/Userland/Libraries/LibGfx/PBMLoader.cpp b/Userland/Libraries/LibGfx/PBMLoader.cpp
new file mode 100644
index 0000000000..4040b09c97
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PBMLoader.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PBMLoader.h"
+#include "PortableImageLoaderCommon.h"
+#include "Streamer.h"
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/StringBuilder.h>
+#include <string.h>
+
+namespace Gfx {
+
+struct PBMLoadingContext {
+ enum Type {
+ Unknown,
+ ASCII,
+ RAWBITS
+ };
+
+ enum State {
+ NotDecoded = 0,
+ Error,
+ MagicNumber,
+ Width,
+ Height,
+ Bitmap,
+ Decoded
+ };
+
+ static constexpr auto ascii_magic_number = '1';
+ static constexpr auto binary_magic_number = '4';
+ static constexpr auto image_type = "PBM";
+
+ Type type { Type::Unknown };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ size_t width { 0 };
+ size_t height { 0 };
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+static bool read_image_data(PBMLoadingContext& context, Streamer& streamer)
+{
+ u8 byte;
+ Vector<Gfx::Color> color_data;
+
+ if (context.type == PBMLoadingContext::ASCII) {
+ while (streamer.read(byte)) {
+ if (byte == '0') {
+ color_data.append(Color::White);
+ } else if (byte == '1') {
+ color_data.append(Color::Black);
+ }
+ }
+ } else if (context.type == PBMLoadingContext::RAWBITS) {
+ size_t color_index = 0;
+
+ while (streamer.read(byte)) {
+ for (int i = 0; i < 8; i++) {
+ int val = byte & 0x80;
+
+ if (val == 0) {
+ color_data.append(Color::White);
+ } else {
+ color_data.append(Color::Black);
+ }
+
+ byte = byte << 1;
+ color_index++;
+
+ if (color_index % context.width == 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ size_t context_size = (u32)context.width * (u32)context.height;
+ if (context_size != color_data.size()) {
+ dbgln("Not enough color data in image.");
+ return false;
+ }
+
+ if (!create_bitmap(context)) {
+ return false;
+ }
+
+ set_pixels(context, color_data);
+
+ context.state = PBMLoadingContext::State::Bitmap;
+ return true;
+}
+
+RefPtr<Gfx::Bitmap> load_pbm(const StringView& path)
+{
+ return load<PBMLoadingContext>(path);
+}
+
+RefPtr<Gfx::Bitmap> load_pbm_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_impl<PBMLoadingContext>(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PBM: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+PBMImageDecoderPlugin::PBMImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PBMLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PBMImageDecoderPlugin::~PBMImageDecoderPlugin()
+{
+}
+
+IntSize PBMImageDecoderPlugin::size()
+{
+ if (m_context->state == PBMLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PBMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PBMImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PBMLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PBMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PBMImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PBMImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PBMImageDecoderPlugin::sniff()
+{
+ if (m_context->data_size < 2)
+ return false;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '1')
+ return true;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '4')
+ return true;
+
+ return false;
+}
+
+bool PBMImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PBMImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PBMImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PBMImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PBMLoader.h b/Userland/Libraries/LibGfx/PBMLoader.h
new file mode 100644
index 0000000000..93f6e45d77
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PBMLoader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_pbm(const StringView& path);
+RefPtr<Gfx::Bitmap> load_pbm_from_memory(const u8*, size_t);
+
+struct PBMLoadingContext;
+
+class PBMImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ PBMImageDecoderPlugin(const u8*, size_t);
+ virtual ~PBMImageDecoderPlugin() override;
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+
+ virtual bool sniff() override;
+
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PBMLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/PGMLoader.cpp b/Userland/Libraries/LibGfx/PGMLoader.cpp
new file mode 100644
index 0000000000..122a698a7b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PGMLoader.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PGMLoader.h"
+#include "PortableImageLoaderCommon.h"
+#include "Streamer.h"
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/StringBuilder.h>
+#include <string.h>
+
+namespace Gfx {
+
+struct PGMLoadingContext {
+ enum Type {
+ Unknown,
+ ASCII,
+ RAWBITS
+ };
+
+ enum State {
+ NotDecoded = 0,
+ Error,
+ MagicNumber,
+ Width,
+ Height,
+ Maxval,
+ Bitmap,
+ Decoded
+ };
+
+ static constexpr auto ascii_magic_number = '2';
+ static constexpr auto binary_magic_number = '5';
+ static constexpr auto image_type = "PGM";
+
+ Type type { Type::Unknown };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ u16 width { 0 };
+ u16 height { 0 };
+ u16 max_val { 0 };
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+static void set_adjusted_pixels(PGMLoadingContext& context, const AK::Vector<Gfx::Color>& color_data)
+{
+ size_t index = 0;
+ for (size_t y = 0; y < context.height; ++y) {
+ for (size_t x = 0; x < context.width; ++x) {
+ Color color = color_data.at(index);
+ if (context.max_val < 255) {
+ color = adjust_color(context.max_val, color);
+ }
+ context.bitmap->set_pixel(x, y, color);
+ ++index;
+ }
+ }
+}
+
+static bool read_image_data(PGMLoadingContext& context, Streamer& streamer)
+{
+ Vector<Gfx::Color> color_data;
+
+ if (context.type == PGMLoadingContext::ASCII) {
+ u16 value;
+
+ while (true) {
+ if (!read_number(streamer, &value))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ color_data.append({ (u8)value, (u8)value, (u8)value });
+ }
+ } else if (context.type == PGMLoadingContext::RAWBITS) {
+ u8 pixel;
+ while (streamer.read(pixel)) {
+ color_data.append({ pixel, pixel, pixel });
+ }
+ }
+
+ size_t context_size = (u32)context.width * (u32)context.height;
+ if (context_size != color_data.size()) {
+ dbgln("Not enough color data in image.");
+ return false;
+ }
+
+ if (!create_bitmap(context))
+ return false;
+
+ set_adjusted_pixels(context, color_data);
+
+ context.state = PGMLoadingContext::State::Bitmap;
+ return true;
+}
+
+RefPtr<Gfx::Bitmap> load_pgm(const StringView& path)
+{
+ return load<PGMLoadingContext>(path);
+}
+
+RefPtr<Gfx::Bitmap> load_pgm_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_impl<PGMLoadingContext>(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PGM: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+PGMImageDecoderPlugin::PGMImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PGMLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PGMImageDecoderPlugin::~PGMImageDecoderPlugin()
+{
+}
+
+IntSize PGMImageDecoderPlugin::size()
+{
+ if (m_context->state == PGMLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PGMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PGMImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PGMLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PGMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PGMImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PGMImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PGMImageDecoderPlugin::sniff()
+{
+ if (m_context->data_size < 2)
+ return false;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '2')
+ return true;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '5')
+ return true;
+
+ return false;
+}
+
+bool PGMImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PGMImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PGMImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PGMImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PGMLoader.h b/Userland/Libraries/LibGfx/PGMLoader.h
new file mode 100644
index 0000000000..ae6afcebaf
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PGMLoader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_pgm(const StringView& path);
+RefPtr<Gfx::Bitmap> load_pgm_from_memory(const u8*, size_t);
+
+struct PGMLoadingContext;
+
+class PGMImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ PGMImageDecoderPlugin(const u8*, size_t);
+ virtual ~PGMImageDecoderPlugin() override;
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+
+ virtual bool sniff() override;
+
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PGMLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/PNGLoader.cpp b/Userland/Libraries/LibGfx/PNGLoader.cpp
new file mode 100644
index 0000000000..48405f8bea
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PNGLoader.cpp
@@ -0,0 +1,1078 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <LibCore/puff.h>
+#include <LibGfx/PNGLoader.h>
+#include <fcntl.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef __serenity__
+# include <serenity.h>
+#endif
+
+//#define PNG_DEBUG
+
+namespace Gfx {
+
+static const u8 png_header[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
+
+struct PNG_IHDR {
+ NetworkOrdered<u32> width;
+ NetworkOrdered<u32> height;
+ u8 bit_depth { 0 };
+ u8 color_type { 0 };
+ u8 compression_method { 0 };
+ u8 filter_method { 0 };
+ u8 interlace_method { 0 };
+};
+
+static_assert(sizeof(PNG_IHDR) == 13);
+
+struct Scanline {
+ u8 filter { 0 };
+ ReadonlyBytes data {};
+};
+
+struct [[gnu::packed]] PaletteEntry {
+ u8 r;
+ u8 g;
+ u8 b;
+ //u8 a;
+};
+
+template<typename T>
+struct [[gnu::packed]] Tuple {
+ T gray;
+ T a;
+};
+
+template<typename T>
+struct [[gnu::packed]] Triplet {
+ T r;
+ T g;
+ T b;
+};
+
+template<typename T>
+struct [[gnu::packed]] Quad {
+ T r;
+ T g;
+ T b;
+ T a;
+};
+
+enum PngInterlaceMethod {
+ Null = 0,
+ Adam7 = 1
+};
+
+struct PNGLoadingContext {
+ enum State {
+ NotDecoded = 0,
+ Error,
+ HeaderDecoded,
+ SizeDecoded,
+ ChunksDecoded,
+ BitmapDecoded,
+ };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ int width { -1 };
+ int height { -1 };
+ u8 bit_depth { 0 };
+ u8 color_type { 0 };
+ u8 compression_method { 0 };
+ u8 filter_method { 0 };
+ u8 interlace_method { 0 };
+ u8 channels { 0 };
+ bool has_seen_zlib_header { false };
+ bool has_alpha() const { return color_type & 4 || palette_transparency_data.size() > 0; }
+ Vector<Scanline> scanlines;
+ RefPtr<Gfx::Bitmap> bitmap;
+ u8* decompression_buffer { nullptr };
+ size_t decompression_buffer_size { 0 };
+ Vector<u8> compressed_data;
+ Vector<PaletteEntry> palette_data;
+ Vector<u8> palette_transparency_data;
+
+ Checked<int> compute_row_size_for_width(int width)
+ {
+ Checked<int> row_size = width;
+ row_size *= channels;
+ row_size *= bit_depth;
+ row_size += 7;
+ row_size /= 8;
+ if (row_size.has_overflow()) {
+ dbgln("PNG too large, integer overflow while computing row size");
+ state = State::Error;
+ }
+ return row_size;
+ }
+};
+
+class Streamer {
+public:
+ Streamer(const u8* data, size_t size)
+ : m_data_ptr(data)
+ , m_size_remaining(size)
+ {
+ }
+
+ template<typename T>
+ bool read(T& value)
+ {
+ if (m_size_remaining < sizeof(T))
+ return false;
+ value = *((const NetworkOrdered<T>*)m_data_ptr);
+ m_data_ptr += sizeof(T);
+ m_size_remaining -= sizeof(T);
+ return true;
+ }
+
+ bool read_bytes(u8* buffer, size_t count)
+ {
+ if (m_size_remaining < count)
+ return false;
+ memcpy(buffer, m_data_ptr, count);
+ m_data_ptr += count;
+ m_size_remaining -= count;
+ return true;
+ }
+
+ bool wrap_bytes(ReadonlyBytes& buffer, size_t count)
+ {
+ if (m_size_remaining < count)
+ return false;
+ buffer = ReadonlyBytes { m_data_ptr, count };
+ m_data_ptr += count;
+ m_size_remaining -= count;
+ return true;
+ }
+
+ bool at_end() const { return !m_size_remaining; }
+
+private:
+ const u8* m_data_ptr { nullptr };
+ size_t m_size_remaining { 0 };
+};
+
+static RefPtr<Gfx::Bitmap> load_png_impl(const u8*, size_t);
+static bool process_chunk(Streamer&, PNGLoadingContext& context);
+
+RefPtr<Gfx::Bitmap> load_png(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_png_impl((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PNG: {}", bitmap->size(), LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+RefPtr<Gfx::Bitmap> load_png_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_png_impl(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PNG: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+ALWAYS_INLINE static u8 paeth_predictor(int a, int b, int c)
+{
+ int p = a + b - c;
+ int pa = abs(p - a);
+ int pb = abs(p - b);
+ int pc = abs(p - c);
+ if (pa <= pb && pa <= pc)
+ return a;
+ if (pb <= pc)
+ return b;
+ return c;
+}
+
+union [[gnu::packed]] Pixel {
+ RGBA32 rgba { 0 };
+ u8 v[4];
+ struct {
+ u8 r;
+ u8 g;
+ u8 b;
+ u8 a;
+ };
+};
+static_assert(sizeof(Pixel) == 4);
+
+template<bool has_alpha, u8 filter_type>
+ALWAYS_INLINE static void unfilter_impl(Gfx::Bitmap& bitmap, int y, const void* dummy_scanline_data)
+{
+ auto* dummy_scanline = (const Pixel*)dummy_scanline_data;
+ if constexpr (filter_type == 0) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ }
+ }
+
+ if constexpr (filter_type == 1) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ swap(pixels[0].r, pixels[0].b);
+ for (int i = 1; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ auto& a = (const Pixel&)pixels[i - 1];
+ x.v[0] += a.v[0];
+ x.v[1] += a.v[1];
+ x.v[2] += a.v[2];
+ if constexpr (has_alpha)
+ x.v[3] += a.v[3];
+ }
+ return;
+ }
+ if constexpr (filter_type == 2) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (const Pixel*)bitmap.scanline(y - 1);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ const Pixel& b = pixels_y_minus_1[i];
+ x.v[0] += b.v[0];
+ x.v[1] += b.v[1];
+ x.v[2] += b.v[2];
+ if constexpr (has_alpha)
+ x.v[3] += b.v[3];
+ }
+ return;
+ }
+ if constexpr (filter_type == 3) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (const Pixel*)bitmap.scanline(y - 1);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ Pixel a;
+ if (i != 0)
+ a = pixels[i - 1];
+ const Pixel& b = pixels_y_minus_1[i];
+ x.v[0] = x.v[0] + ((a.v[0] + b.v[0]) / 2);
+ x.v[1] = x.v[1] + ((a.v[1] + b.v[1]) / 2);
+ x.v[2] = x.v[2] + ((a.v[2] + b.v[2]) / 2);
+ if constexpr (has_alpha)
+ x.v[3] = x.v[3] + ((a.v[3] + b.v[3]) / 2);
+ }
+ return;
+ }
+ if constexpr (filter_type == 4) {
+ auto* pixels = (Pixel*)bitmap.scanline(y);
+ auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
+ for (int i = 0; i < bitmap.width(); ++i) {
+ auto& x = pixels[i];
+ swap(x.r, x.b);
+ Pixel a;
+ const Pixel& b = pixels_y_minus_1[i];
+ Pixel c;
+ if (i != 0) {
+ a = pixels[i - 1];
+ c = pixels_y_minus_1[i - 1];
+ }
+ x.v[0] += paeth_predictor(a.v[0], b.v[0], c.v[0]);
+ x.v[1] += paeth_predictor(a.v[1], b.v[1], c.v[1]);
+ x.v[2] += paeth_predictor(a.v[2], b.v[2], c.v[2]);
+ if constexpr (has_alpha)
+ x.v[3] += paeth_predictor(a.v[3], b.v[3], c.v[3]);
+ }
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static void unpack_grayscale_without_alpha(PNGLoadingContext& context)
+{
+ for (int y = 0; y < context.height; ++y) {
+ auto* gray_values = reinterpret_cast<const T*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = gray_values[i];
+ pixel.g = gray_values[i];
+ pixel.b = gray_values[i];
+ pixel.a = 0xff;
+ }
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static void unpack_grayscale_with_alpha(PNGLoadingContext& context)
+{
+ for (int y = 0; y < context.height; ++y) {
+ auto* tuples = reinterpret_cast<const Tuple<T>*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = tuples[i].gray;
+ pixel.g = tuples[i].gray;
+ pixel.b = tuples[i].gray;
+ pixel.a = tuples[i].a;
+ }
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static void unpack_triplets_without_alpha(PNGLoadingContext& context)
+{
+ for (int y = 0; y < context.height; ++y) {
+ auto* triplets = reinterpret_cast<const Triplet<T>*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = triplets[i].r;
+ pixel.g = triplets[i].g;
+ pixel.b = triplets[i].b;
+ pixel.a = 0xff;
+ }
+ }
+}
+
+NEVER_INLINE FLATTEN static bool unfilter(PNGLoadingContext& context)
+{
+ // First unpack the scanlines to RGBA:
+ switch (context.color_type) {
+ case 0:
+ if (context.bit_depth == 8) {
+ unpack_grayscale_without_alpha<u8>(context);
+ } else if (context.bit_depth == 16) {
+ unpack_grayscale_without_alpha<u16>(context);
+ } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) {
+ auto bit_depth_squared = context.bit_depth * context.bit_depth;
+ auto pixels_per_byte = 8 / context.bit_depth;
+ auto mask = (1 << context.bit_depth) - 1;
+ for (int y = 0; y < context.height; ++y) {
+ auto* gray_values = context.scanlines[y].data.data();
+ for (int x = 0; x < context.width; ++x) {
+ auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (x % pixels_per_byte));
+ auto value = (gray_values[x / pixels_per_byte] >> bit_offset) & mask;
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[x];
+ pixel.r = value * (0xff / bit_depth_squared);
+ pixel.g = value * (0xff / bit_depth_squared);
+ pixel.b = value * (0xff / bit_depth_squared);
+ pixel.a = 0xff;
+ }
+ }
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 4:
+ if (context.bit_depth == 8) {
+ unpack_grayscale_with_alpha<u8>(context);
+ } else if (context.bit_depth == 16) {
+ unpack_grayscale_with_alpha<u16>(context);
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 2:
+ if (context.bit_depth == 8) {
+ unpack_triplets_without_alpha<u8>(context);
+ } else if (context.bit_depth == 16) {
+ unpack_triplets_without_alpha<u16>(context);
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 6:
+ if (context.bit_depth == 8) {
+ for (int y = 0; y < context.height; ++y) {
+ memcpy(context.bitmap->scanline(y), context.scanlines[y].data.data(), context.scanlines[y].data.size());
+ }
+ } else if (context.bit_depth == 16) {
+ for (int y = 0; y < context.height; ++y) {
+ auto* triplets = reinterpret_cast<const Quad<u16>*>(context.scanlines[y].data.data());
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ pixel.r = triplets[i].r & 0xFF;
+ pixel.g = triplets[i].g & 0xFF;
+ pixel.b = triplets[i].b & 0xFF;
+ pixel.a = triplets[i].a & 0xFF;
+ }
+ }
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ case 3:
+ if (context.bit_depth == 8) {
+ for (int y = 0; y < context.height; ++y) {
+ auto* palette_index = context.scanlines[y].data.data();
+ for (int i = 0; i < context.width; ++i) {
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ if (palette_index[i] >= context.palette_data.size())
+ return false;
+ auto& color = context.palette_data.at((int)palette_index[i]);
+ auto transparency = context.palette_transparency_data.size() >= palette_index[i] + 1u
+ ? context.palette_transparency_data.data()[palette_index[i]]
+ : 0xff;
+ pixel.r = color.r;
+ pixel.g = color.g;
+ pixel.b = color.b;
+ pixel.a = transparency;
+ }
+ }
+ } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) {
+ auto pixels_per_byte = 8 / context.bit_depth;
+ auto mask = (1 << context.bit_depth) - 1;
+ for (int y = 0; y < context.height; ++y) {
+ auto* palette_indexes = context.scanlines[y].data.data();
+ for (int i = 0; i < context.width; ++i) {
+ auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (i % pixels_per_byte));
+ auto palette_index = (palette_indexes[i / pixels_per_byte] >> bit_offset) & mask;
+ auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
+ if ((size_t)palette_index >= context.palette_data.size())
+ return false;
+ auto& color = context.palette_data.at(palette_index);
+ auto transparency = context.palette_transparency_data.size() >= palette_index + 1u
+ ? context.palette_transparency_data.data()[palette_index]
+ : 0xff;
+ pixel.r = color.r;
+ pixel.g = color.g;
+ pixel.b = color.b;
+ pixel.a = transparency;
+ }
+ }
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ u8 dummy_scanline[context.width * sizeof(RGBA32)];
+
+ for (int y = 0; y < context.height; ++y) {
+ auto filter = context.scanlines[y].filter;
+ if (filter == 0) {
+ if (context.has_alpha())
+ unfilter_impl<true, 0>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 0>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 1) {
+ if (context.has_alpha())
+ unfilter_impl<true, 1>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 1>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 2) {
+ if (context.has_alpha())
+ unfilter_impl<true, 2>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 2>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 3) {
+ if (context.has_alpha())
+ unfilter_impl<true, 3>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 3>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ if (filter == 4) {
+ if (context.has_alpha())
+ unfilter_impl<true, 4>(*context.bitmap, y, dummy_scanline);
+ else
+ unfilter_impl<false, 4>(*context.bitmap, y, dummy_scanline);
+ continue;
+ }
+ }
+
+ return true;
+}
+
+static bool decode_png_header(PNGLoadingContext& context)
+{
+ if (context.state >= PNGLoadingContext::HeaderDecoded)
+ return true;
+
+ if (!context.data || context.data_size < sizeof(png_header)) {
+#ifdef PNG_DEBUG
+ dbgln("Missing PNG header");
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ if (memcmp(context.data, png_header, sizeof(png_header)) != 0) {
+#ifdef PNG_DEBUG
+ dbgln("Invalid PNG header");
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ context.state = PNGLoadingContext::HeaderDecoded;
+ return true;
+}
+
+static bool decode_png_size(PNGLoadingContext& context)
+{
+ if (context.state >= PNGLoadingContext::SizeDecoded)
+ return true;
+
+ if (context.state < PNGLoadingContext::HeaderDecoded) {
+ if (!decode_png_header(context))
+ return false;
+ }
+
+ const u8* data_ptr = context.data + sizeof(png_header);
+ size_t data_remaining = context.data_size - sizeof(png_header);
+
+ Streamer streamer(data_ptr, data_remaining);
+ while (!streamer.at_end()) {
+ if (!process_chunk(streamer, context)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ if (context.width && context.height) {
+ context.state = PNGLoadingContext::State::SizeDecoded;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool decode_png_chunks(PNGLoadingContext& context)
+{
+ if (context.state >= PNGLoadingContext::State::ChunksDecoded)
+ return true;
+
+ if (context.state < PNGLoadingContext::HeaderDecoded) {
+ if (!decode_png_header(context))
+ return false;
+ }
+
+ const u8* data_ptr = context.data + sizeof(png_header);
+ int data_remaining = context.data_size - sizeof(png_header);
+
+ context.compressed_data.ensure_capacity(context.data_size);
+
+ Streamer streamer(data_ptr, data_remaining);
+ while (!streamer.at_end()) {
+ if (!process_chunk(streamer, context)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ }
+
+ context.state = PNGLoadingContext::State::ChunksDecoded;
+ return true;
+}
+
+static bool decode_png_bitmap_simple(PNGLoadingContext& context)
+{
+ Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
+
+ for (int y = 0; y < context.height; ++y) {
+ u8 filter;
+ if (!streamer.read(filter)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ if (filter > 4) {
+#ifdef PNG_DEBUG
+ dbg() << "Invalid PNG filter: " << filter;
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ context.scanlines.append({ filter });
+ auto& scanline_buffer = context.scanlines.last().data;
+ auto row_size = context.compute_row_size_for_width(context.width);
+ if (row_size.has_overflow())
+ return false;
+
+ if (!streamer.wrap_bytes(scanline_buffer, row_size.value())) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ }
+
+ context.bitmap = Bitmap::create_purgeable(context.has_alpha() ? BitmapFormat::RGBA32 : BitmapFormat::RGB32, { context.width, context.height });
+
+ if (!context.bitmap) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ return unfilter(context);
+}
+
+static int adam7_height(PNGLoadingContext& context, int pass)
+{
+ switch (pass) {
+ case 1:
+ return (context.height + 7) / 8;
+ case 2:
+ return (context.height + 7) / 8;
+ case 3:
+ return (context.height + 3) / 8;
+ case 4:
+ return (context.height + 3) / 4;
+ case 5:
+ return (context.height + 1) / 4;
+ case 6:
+ return (context.height + 1) / 2;
+ case 7:
+ return context.height / 2;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+static int adam7_width(PNGLoadingContext& context, int pass)
+{
+ switch (pass) {
+ case 1:
+ return (context.width + 7) / 8;
+ case 2:
+ return (context.width + 3) / 8;
+ case 3:
+ return (context.width + 3) / 4;
+ case 4:
+ return (context.width + 1) / 4;
+ case 5:
+ return (context.width + 1) / 2;
+ case 6:
+ return context.width / 2;
+ case 7:
+ return context.width;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+// Index 0 unused (non-interlaced case)
+static int adam7_starty[8] = { 0, 0, 0, 4, 0, 2, 0, 1 };
+static int adam7_startx[8] = { 0, 0, 4, 0, 2, 0, 1, 0 };
+static int adam7_stepy[8] = { 1, 8, 8, 8, 4, 4, 2, 2 };
+static int adam7_stepx[8] = { 1, 8, 8, 4, 4, 2, 2, 1 };
+
+static bool decode_adam7_pass(PNGLoadingContext& context, Streamer& streamer, int pass)
+{
+ PNGLoadingContext subimage_context;
+ subimage_context.width = adam7_width(context, pass);
+ subimage_context.height = adam7_height(context, pass);
+ subimage_context.channels = context.channels;
+ subimage_context.color_type = context.color_type;
+ subimage_context.palette_data = context.palette_data;
+ subimage_context.palette_transparency_data = context.palette_transparency_data;
+ subimage_context.bit_depth = context.bit_depth;
+ subimage_context.filter_method = context.filter_method;
+
+ // For small images, some passes might be empty
+ if (!subimage_context.width || !subimage_context.height)
+ return true;
+
+ subimage_context.scanlines.clear_with_capacity();
+ for (int y = 0; y < subimage_context.height; ++y) {
+ u8 filter;
+ if (!streamer.read(filter)) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ if (filter > 4) {
+#ifdef PNG_DEBUG
+ dbg() << "Invalid PNG filter: " << filter;
+#endif
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+
+ subimage_context.scanlines.append({ filter });
+ auto& scanline_buffer = subimage_context.scanlines.last().data;
+
+ auto row_size = context.compute_row_size_for_width(subimage_context.width);
+ if (row_size.has_overflow())
+ return false;
+ if (!streamer.wrap_bytes(scanline_buffer, row_size.value())) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ }
+
+ subimage_context.bitmap = Bitmap::create(context.bitmap->format(), { subimage_context.width, subimage_context.height });
+ if (!unfilter(subimage_context)) {
+ subimage_context.bitmap = nullptr;
+ return false;
+ }
+
+ // Copy the subimage data into the main image according to the pass pattern
+ for (int y = 0, dy = adam7_starty[pass]; y < subimage_context.height && dy < context.height; ++y, dy += adam7_stepy[pass]) {
+ for (int x = 0, dx = adam7_startx[pass]; x < subimage_context.width && dy < context.width; ++x, dx += adam7_stepx[pass]) {
+ context.bitmap->set_pixel(dx, dy, subimage_context.bitmap->get_pixel(x, y));
+ }
+ }
+ return true;
+}
+
+static bool decode_png_adam7(PNGLoadingContext& context)
+{
+ Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
+ context.bitmap = Bitmap::create_purgeable(context.has_alpha() ? BitmapFormat::RGBA32 : BitmapFormat::RGB32, { context.width, context.height });
+ if (!context.bitmap)
+ return false;
+
+ for (int pass = 1; pass <= 7; ++pass) {
+ if (!decode_adam7_pass(context, streamer, pass))
+ return false;
+ }
+ return true;
+}
+
+static bool decode_png_bitmap(PNGLoadingContext& context)
+{
+ if (context.state < PNGLoadingContext::State::ChunksDecoded) {
+ if (!decode_png_chunks(context))
+ return false;
+ }
+
+ if (context.state >= PNGLoadingContext::State::BitmapDecoded)
+ return true;
+
+ if (context.width == -1 || context.height == -1)
+ return false; // Didn't see an IHDR chunk.
+
+ if (context.color_type == 3 && context.palette_data.is_empty())
+ return false; // Didn't see a PLTE chunk for a palettized image, or it was empty.
+
+ unsigned long srclen = context.compressed_data.size() - 6;
+ unsigned long destlen = 0;
+ int ret = puff(nullptr, &destlen, context.compressed_data.data() + 2, &srclen);
+ if (ret != 0) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ context.decompression_buffer_size = destlen;
+#ifdef __serenity__
+ context.decompression_buffer = (u8*)mmap_with_name(nullptr, context.decompression_buffer_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, "PNG decompression buffer");
+#else
+ context.decompression_buffer = (u8*)mmap(nullptr, context.decompression_buffer_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
+#endif
+
+ ret = puff(context.decompression_buffer, &destlen, context.compressed_data.data() + 2, &srclen);
+ if (ret != 0) {
+ context.state = PNGLoadingContext::State::Error;
+ return false;
+ }
+ context.compressed_data.clear();
+
+ context.scanlines.ensure_capacity(context.height);
+ switch (context.interlace_method) {
+ case PngInterlaceMethod::Null:
+ if (!decode_png_bitmap_simple(context))
+ return false;
+ break;
+ case PngInterlaceMethod::Adam7:
+ if (!decode_png_adam7(context))
+ return false;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ munmap(context.decompression_buffer, context.decompression_buffer_size);
+ context.decompression_buffer = nullptr;
+ context.decompression_buffer_size = 0;
+
+ context.state = PNGLoadingContext::State::BitmapDecoded;
+ return true;
+}
+
+static RefPtr<Gfx::Bitmap> load_png_impl(const u8* data, size_t data_size)
+{
+ PNGLoadingContext context;
+ context.data = data;
+ context.data_size = data_size;
+
+ if (!decode_png_chunks(context))
+ return nullptr;
+
+ if (!decode_png_bitmap(context))
+ return nullptr;
+
+ return context.bitmap;
+}
+
+static bool is_valid_compression_method(u8 compression_method)
+{
+ return compression_method == 0;
+}
+
+static bool is_valid_filter_method(u8 filter_method)
+{
+ return filter_method <= 4;
+}
+
+static bool process_IHDR(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ if (data.size() < (int)sizeof(PNG_IHDR))
+ return false;
+ auto& ihdr = *(const PNG_IHDR*)data.data();
+
+ if (ihdr.width > maximum_width_for_decoded_images || ihdr.height > maximum_height_for_decoded_images) {
+ dbgln("This PNG is too large for comfort: {}x{}", (u32)ihdr.width, (u32)ihdr.height);
+ return false;
+ }
+
+ if (!is_valid_compression_method(ihdr.compression_method)) {
+ dbgln("PNG has invalid compression method {}", ihdr.compression_method);
+ return false;
+ }
+
+ if (!is_valid_filter_method(ihdr.filter_method)) {
+ dbgln("PNG has invalid filter method {}", ihdr.filter_method);
+ return false;
+ }
+
+ context.width = ihdr.width;
+ context.height = ihdr.height;
+ context.bit_depth = ihdr.bit_depth;
+ context.color_type = ihdr.color_type;
+ context.compression_method = ihdr.compression_method;
+ context.filter_method = ihdr.filter_method;
+ context.interlace_method = ihdr.interlace_method;
+
+#ifdef PNG_DEBUG
+ printf("PNG: %dx%d (%d bpp)\n", context.width, context.height, context.bit_depth);
+ printf(" Color type: %d\n", context.color_type);
+ printf("Compress Method: %d\n", context.compression_method);
+ printf(" Filter Method: %d\n", context.filter_method);
+ printf(" Interlace type: %d\n", context.interlace_method);
+#endif
+
+ if (context.interlace_method != PngInterlaceMethod::Null && context.interlace_method != PngInterlaceMethod::Adam7) {
+#ifdef PNG_DEBUG
+ dbgln("PNGLoader::process_IHDR: unknown interlace method: {}", context.interlace_method);
+#endif
+ return false;
+ }
+
+ switch (context.color_type) {
+ case 0: // Each pixel is a grayscale sample.
+ if (context.bit_depth != 1 && context.bit_depth != 2 && context.bit_depth != 4 && context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 1;
+ break;
+ case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
+ if (context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 2;
+ break;
+ case 2: // Each pixel is an RGB sample
+ if (context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 3;
+ break;
+ case 3: // Each pixel is a palette index; a PLTE chunk must appear.
+ if (context.bit_depth != 1 && context.bit_depth != 2 && context.bit_depth != 4 && context.bit_depth != 8)
+ return false;
+ context.channels = 1;
+ break;
+ case 6: // Each pixel is an RGB sample, followed by an alpha sample.
+ if (context.bit_depth != 8 && context.bit_depth != 16)
+ return false;
+ context.channels = 4;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static bool process_IDAT(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ context.compressed_data.append(data.data(), data.size());
+ return true;
+}
+
+static bool process_PLTE(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ context.palette_data.append((const PaletteEntry*)data.data(), data.size() / 3);
+ return true;
+}
+
+static bool process_tRNS(ReadonlyBytes data, PNGLoadingContext& context)
+{
+ switch (context.color_type) {
+ case 3:
+ context.palette_transparency_data.append(data.data(), data.size());
+ break;
+ }
+ return true;
+}
+
+static bool process_chunk(Streamer& streamer, PNGLoadingContext& context)
+{
+ u32 chunk_size;
+ if (!streamer.read(chunk_size)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_size\n");
+#endif
+ return false;
+ }
+ u8 chunk_type[5];
+ chunk_type[4] = '\0';
+ if (!streamer.read_bytes(chunk_type, 4)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_type\n");
+#endif
+ return false;
+ }
+ ReadonlyBytes chunk_data;
+ if (!streamer.wrap_bytes(chunk_data, chunk_size)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_data\n");
+#endif
+ return false;
+ }
+ u32 chunk_crc;
+ if (!streamer.read(chunk_crc)) {
+#ifdef PNG_DEBUG
+ printf("Bail at chunk_crc\n");
+#endif
+ return false;
+ }
+#ifdef PNG_DEBUG
+ printf("Chunk type: '%s', size: %u, crc: %x\n", chunk_type, chunk_size, chunk_crc);
+#endif
+
+ if (!strcmp((const char*)chunk_type, "IHDR"))
+ return process_IHDR(chunk_data, context);
+ if (!strcmp((const char*)chunk_type, "IDAT"))
+ return process_IDAT(chunk_data, context);
+ if (!strcmp((const char*)chunk_type, "PLTE"))
+ return process_PLTE(chunk_data, context);
+ if (!strcmp((const char*)chunk_type, "tRNS"))
+ return process_tRNS(chunk_data, context);
+ return true;
+}
+
+PNGImageDecoderPlugin::PNGImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PNGLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PNGImageDecoderPlugin::~PNGImageDecoderPlugin()
+{
+}
+
+IntSize PNGImageDecoderPlugin::size()
+{
+ if (m_context->state == PNGLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PNGLoadingContext::State::SizeDecoded) {
+ bool success = decode_png_size(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PNGImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PNGLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PNGLoadingContext::State::BitmapDecoded) {
+ // NOTE: This forces the chunk decoding to happen.
+ bool success = decode_png_bitmap(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PNGImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PNGImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PNGImageDecoderPlugin::sniff()
+{
+ return decode_png_header(*m_context);
+}
+
+bool PNGImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PNGImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PNGImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PNGImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PNGLoader.h b/Userland/Libraries/LibGfx/PNGLoader.h
new file mode 100644
index 0000000000..2432f231e0
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PNGLoader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_png(const StringView& path);
+RefPtr<Gfx::Bitmap> load_png_from_memory(const u8*, size_t);
+
+struct PNGLoadingContext;
+
+class PNGImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ virtual ~PNGImageDecoderPlugin() override;
+ PNGImageDecoderPlugin(const u8*, size_t);
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+ virtual bool sniff() override;
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PNGLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/PPMLoader.cpp b/Userland/Libraries/LibGfx/PPMLoader.cpp
new file mode 100644
index 0000000000..80d99d8966
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PPMLoader.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PPMLoader.h"
+#include "PortableImageLoaderCommon.h"
+#include "Streamer.h"
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/ScopeGuard.h>
+#include <AK/StringBuilder.h>
+#include <string.h>
+
+namespace Gfx {
+
+struct PPMLoadingContext {
+ enum Type {
+ Unknown,
+ ASCII,
+ RAWBITS
+ };
+
+ enum State {
+ NotDecoded = 0,
+ Error,
+ MagicNumber,
+ Width,
+ Height,
+ Maxval,
+ Bitmap,
+ Decoded
+ };
+
+ static constexpr auto ascii_magic_number = '3';
+ static constexpr auto binary_magic_number = '6';
+ static constexpr auto image_type = "PPM";
+
+ Type type { Type::Unknown };
+ State state { State::NotDecoded };
+ const u8* data { nullptr };
+ size_t data_size { 0 };
+ u16 width { 0 };
+ u16 height { 0 };
+ u16 max_val { 0 };
+ RefPtr<Gfx::Bitmap> bitmap;
+};
+
+static bool read_image_data(PPMLoadingContext& context, Streamer& streamer)
+{
+ Vector<Gfx::Color> color_data;
+ color_data.ensure_capacity(context.width * context.height);
+
+ if (context.type == PPMLoadingContext::ASCII) {
+ u16 red;
+ u16 green;
+ u16 blue;
+
+ while (true) {
+ if (!read_number(streamer, &red))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ if (!read_number(streamer, &green))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ if (!read_number(streamer, &blue))
+ break;
+
+ if (!read_white_space(context, streamer))
+ break;
+
+ Color color { (u8)red, (u8)green, (u8)blue };
+ if (context.max_val < 255)
+ color = adjust_color(context.max_val, color);
+ color_data.append(color);
+ }
+ } else if (context.type == PPMLoadingContext::RAWBITS) {
+ u8 pixel[3];
+ while (streamer.read_bytes(pixel, 3)) {
+ color_data.append({ pixel[0], pixel[1], pixel[2] });
+ }
+ }
+
+ if (context.width * context.height != color_data.size())
+ return false;
+
+ if (!create_bitmap(context)) {
+ return false;
+ }
+
+ set_pixels(context, color_data);
+
+ context.state = PPMLoadingContext::State::Bitmap;
+ return true;
+}
+
+RefPtr<Gfx::Bitmap> load_ppm(const StringView& path)
+{
+ return load<PPMLoadingContext>(path);
+}
+
+RefPtr<Gfx::Bitmap> load_ppm_from_memory(const u8* data, size_t length)
+{
+ auto bitmap = load_impl<PPMLoadingContext>(data, length);
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded PPM: <memory>", bitmap->size()));
+ return bitmap;
+}
+
+PPMImageDecoderPlugin::PPMImageDecoderPlugin(const u8* data, size_t size)
+{
+ m_context = make<PPMLoadingContext>();
+ m_context->data = data;
+ m_context->data_size = size;
+}
+
+PPMImageDecoderPlugin::~PPMImageDecoderPlugin()
+{
+}
+
+IntSize PPMImageDecoderPlugin::size()
+{
+ if (m_context->state == PPMLoadingContext::State::Error)
+ return {};
+
+ if (m_context->state < PPMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return {};
+ }
+
+ return { m_context->width, m_context->height };
+}
+
+RefPtr<Gfx::Bitmap> PPMImageDecoderPlugin::bitmap()
+{
+ if (m_context->state == PPMLoadingContext::State::Error)
+ return nullptr;
+
+ if (m_context->state < PPMLoadingContext::State::Decoded) {
+ bool success = decode(*m_context);
+ if (!success)
+ return nullptr;
+ }
+
+ ASSERT(m_context->bitmap);
+ return m_context->bitmap;
+}
+
+void PPMImageDecoderPlugin::set_volatile()
+{
+ if (m_context->bitmap)
+ m_context->bitmap->set_volatile();
+}
+
+bool PPMImageDecoderPlugin::set_nonvolatile()
+{
+ if (!m_context->bitmap)
+ return false;
+
+ return m_context->bitmap->set_nonvolatile();
+}
+
+bool PPMImageDecoderPlugin::sniff()
+{
+ if (m_context->data_size < 2)
+ return false;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '3')
+ return true;
+
+ if (m_context->data[0] == 'P' && m_context->data[1] == '6')
+ return true;
+
+ return false;
+}
+
+bool PPMImageDecoderPlugin::is_animated()
+{
+ return false;
+}
+
+size_t PPMImageDecoderPlugin::loop_count()
+{
+ return 0;
+}
+
+size_t PPMImageDecoderPlugin::frame_count()
+{
+ return 1;
+}
+
+ImageFrameDescriptor PPMImageDecoderPlugin::frame(size_t i)
+{
+ if (i > 0) {
+ return { bitmap(), 0 };
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/PPMLoader.h b/Userland/Libraries/LibGfx/PPMLoader.h
new file mode 100644
index 0000000000..bddd019d76
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PPMLoader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ImageDecoder.h>
+
+namespace Gfx {
+
+RefPtr<Gfx::Bitmap> load_ppm(const StringView& path);
+RefPtr<Gfx::Bitmap> load_ppm_from_memory(const u8*, size_t);
+
+struct PPMLoadingContext;
+
+class PPMImageDecoderPlugin final : public ImageDecoderPlugin {
+public:
+ PPMImageDecoderPlugin(const u8*, size_t);
+ virtual ~PPMImageDecoderPlugin() override;
+
+ virtual IntSize size() override;
+ virtual RefPtr<Gfx::Bitmap> bitmap() override;
+
+ virtual void set_volatile() override;
+ [[nodiscard]] virtual bool set_nonvolatile() override;
+
+ virtual bool sniff() override;
+
+ virtual bool is_animated() override;
+ virtual size_t loop_count() override;
+ virtual size_t frame_count() override;
+ virtual ImageFrameDescriptor frame(size_t i) override;
+
+private:
+ OwnPtr<PPMLoadingContext> m_context;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp
new file mode 100644
index 0000000000..7e7f5f16c7
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Painter.cpp
@@ -0,0 +1,1629 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Painter.h"
+#include "Bitmap.h"
+#include "Emoji.h"
+#include "Font.h"
+#include "FontDatabase.h"
+#include "Gamma.h"
+#include <AK/Assertions.h>
+#include <AK/Function.h>
+#include <AK/Memory.h>
+#include <AK/QuickSort.h>
+#include <AK/StdLibExtras.h>
+#include <AK/StringBuilder.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <LibGfx/CharacterBitmap.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/Path.h>
+#include <math.h>
+#include <stdio.h>
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC optimize("O3")
+#endif
+
+namespace Gfx {
+
+template<BitmapFormat format = BitmapFormat::Invalid>
+ALWAYS_INLINE Color get_pixel(const Gfx::Bitmap& bitmap, int x, int y)
+{
+ if constexpr (format == BitmapFormat::Indexed8)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::Indexed4)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::Indexed2)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::Indexed1)
+ return bitmap.palette_color(bitmap.scanline_u8(y)[x]);
+ if constexpr (format == BitmapFormat::RGB32)
+ return Color::from_rgb(bitmap.scanline(y)[x]);
+ if constexpr (format == BitmapFormat::RGBA32)
+ return Color::from_rgba(bitmap.scanline(y)[x]);
+ return bitmap.get_pixel(x, y);
+}
+
+Painter::Painter(Gfx::Bitmap& bitmap)
+ : m_target(bitmap)
+{
+ ASSERT(bitmap.format() == Gfx::BitmapFormat::RGB32 || bitmap.format() == Gfx::BitmapFormat::RGBA32);
+ m_state_stack.append(State());
+ state().font = &FontDatabase::default_font();
+ state().clip_rect = { { 0, 0 }, bitmap.size() };
+ m_clip_origin = state().clip_rect;
+}
+
+Painter::~Painter()
+{
+}
+
+void Painter::fill_rect_with_draw_op(const IntRect& a_rect, Color color)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = rect.height() - 1; i >= 0; --i) {
+ for (int j = 0; j < rect.width(); ++j)
+ set_pixel_with_draw_op(dst[j], color);
+ dst += dst_skip;
+ }
+}
+
+void Painter::clear_rect(const IntRect& a_rect, Color color)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ ASSERT(m_target->rect().contains(rect));
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = rect.height() - 1; i >= 0; --i) {
+ fast_u32_fill(dst, color.value(), rect.width());
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect(const IntRect& a_rect, Color color)
+{
+ if (color.alpha() == 0)
+ return;
+
+ if (draw_op() != DrawOp::Copy) {
+ fill_rect_with_draw_op(a_rect, color);
+ return;
+ }
+
+ if (color.alpha() == 0xff) {
+ clear_rect(a_rect, color);
+ return;
+ }
+
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ ASSERT(m_target->rect().contains(rect));
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = rect.height() - 1; i >= 0; --i) {
+ for (int j = 0; j < rect.width(); ++j)
+ dst[j] = Color::from_rgba(dst[j]).blend(color).value();
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect_with_dither_pattern(const IntRect& a_rect, Color color_a, Color color_b)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = 0; i < rect.height(); ++i) {
+ for (int j = 0; j < rect.width(); ++j) {
+ bool checkboard_use_a = (i & 1) ^ (j & 1);
+ if (checkboard_use_a && !color_a.alpha())
+ continue;
+ if (!checkboard_use_a && !color_b.alpha())
+ continue;
+ dst[j] = checkboard_use_a ? color_a.value() : color_b.value();
+ }
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect_with_checkerboard(const IntRect& a_rect, const IntSize& cell_size, Color color_dark, Color color_light)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = 0; i < rect.height(); ++i) {
+ for (int j = 0; j < rect.width(); ++j) {
+ int cell_row = i / cell_size.height();
+ int cell_col = j / cell_size.width();
+ dst[j] = ((cell_row % 2) ^ (cell_col % 2)) ? color_light.value() : color_dark.value();
+ }
+ dst += dst_skip;
+ }
+}
+
+void Painter::fill_rect_with_gradient(Orientation orientation, const IntRect& a_rect, Color gradient_start, Color gradient_end)
+{
+#ifdef NO_FPU
+ return fill_rect(a_rect, gradient_start);
+#endif
+ auto rect = a_rect.translated(translation());
+ auto clipped_rect = IntRect::intersection(rect, clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+
+ int offset = clipped_rect.primary_offset_for_orientation(orientation) - rect.primary_offset_for_orientation(orientation);
+
+ RGBA32* dst = m_target->scanline(clipped_rect.top()) + clipped_rect.left();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ float increment = (1.0 / ((rect.primary_size_for_orientation(orientation))));
+
+ if (orientation == Orientation::Horizontal) {
+ for (int i = clipped_rect.height() - 1; i >= 0; --i) {
+ float c = offset * increment;
+ for (int j = 0; j < clipped_rect.width(); ++j) {
+ dst[j] = gamma_accurate_blend(gradient_start, gradient_end, c).value();
+ c += increment;
+ }
+ dst += dst_skip;
+ }
+ } else {
+ float c = offset * increment;
+ for (int i = clipped_rect.height() - 1; i >= 0; --i) {
+ auto color = gamma_accurate_blend(gradient_start, gradient_end, c);
+ for (int j = 0; j < clipped_rect.width(); ++j) {
+ dst[j] = color.value();
+ }
+ c += increment;
+ dst += dst_skip;
+ }
+ }
+}
+
+void Painter::fill_rect_with_gradient(const IntRect& a_rect, Color gradient_start, Color gradient_end)
+{
+ return fill_rect_with_gradient(Orientation::Horizontal, a_rect, gradient_start, gradient_end);
+}
+
+void Painter::fill_ellipse(const IntRect& a_rect, Color color)
+{
+ auto rect = a_rect.translated(translation()).intersected(clip_rect());
+ if (rect.is_empty())
+ return;
+
+ ASSERT(m_target->rect().contains(rect));
+
+ RGBA32* dst = m_target->scanline(rect.top()) + rect.left() + rect.width() / 2;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int i = 0; i < rect.height(); i++) {
+ double y = rect.height() * 0.5 - i;
+ double x = rect.width() * sqrt(0.25 - y * y / rect.height() / rect.height());
+ fast_u32_fill(dst - (int)x, color.value(), 2 * (int)x);
+ dst += dst_skip;
+ }
+}
+
+void Painter::draw_ellipse_intersecting(const IntRect& rect, Color color, int thickness)
+{
+ constexpr int number_samples = 100; // FIXME: dynamically work out the number of samples based upon the rect size
+ double increment = M_PI / number_samples;
+
+ auto ellipse_x = [&](double theta) -> int {
+ return (cos(theta) * rect.width() / sqrt(2)) + rect.center().x();
+ };
+
+ auto ellipse_y = [&](double theta) -> int {
+ return (sin(theta) * rect.height() / sqrt(2)) + rect.center().y();
+ };
+
+ for (float theta = 0; theta < 2 * M_PI; theta += increment) {
+ draw_line({ ellipse_x(theta), ellipse_y(theta) }, { ellipse_x(theta + increment), ellipse_y(theta + increment) }, color, thickness);
+ }
+}
+
+template<typename RectType, typename Callback>
+static void for_each_pixel_around_rect_clockwise(const RectType& rect, Callback callback)
+{
+ if (rect.is_empty())
+ return;
+ for (auto x = rect.left(); x <= rect.right(); ++x) {
+ callback(x, rect.top());
+ }
+ for (auto y = rect.top() + 1; y <= rect.bottom(); ++y) {
+ callback(rect.right(), y);
+ }
+ for (auto x = rect.right() - 1; x >= rect.left(); --x) {
+ callback(x, rect.bottom());
+ }
+ for (auto y = rect.bottom() - 1; y > rect.top(); --y) {
+ callback(rect.left(), y);
+ }
+}
+
+void Painter::draw_focus_rect(const IntRect& rect, Color color)
+{
+ if (rect.is_empty())
+ return;
+ bool state = false;
+ for_each_pixel_around_rect_clockwise(rect, [&](auto x, auto y) {
+ if (state)
+ set_pixel(x, y, color);
+ state = !state;
+ });
+}
+
+void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough)
+{
+ IntRect rect = a_rect.translated(translation());
+ auto clipped_rect = rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+
+ int min_y = clipped_rect.top();
+ int max_y = clipped_rect.bottom();
+
+ if (rect.top() >= clipped_rect.top() && rect.top() <= clipped_rect.bottom()) {
+ int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
+ int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
+ fill_scanline_with_draw_op(rect.top(), start_x, width, color);
+ ++min_y;
+ }
+ if (rect.bottom() >= clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) {
+ int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
+ int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
+ fill_scanline_with_draw_op(rect.bottom(), start_x, width, color);
+ --max_y;
+ }
+
+ bool draw_left_side = rect.left() >= clipped_rect.left();
+ bool draw_right_side = rect.right() == clipped_rect.right();
+
+ if (draw_left_side && draw_right_side) {
+ // Specialized loop when drawing both sides.
+ for (int y = min_y; y <= max_y; ++y) {
+ auto* bits = m_target->scanline(y);
+ set_pixel_with_draw_op(bits[rect.left()], color);
+ set_pixel_with_draw_op(bits[rect.right()], color);
+ }
+ } else {
+ for (int y = min_y; y <= max_y; ++y) {
+ auto* bits = m_target->scanline(y);
+ if (draw_left_side)
+ set_pixel_with_draw_op(bits[rect.left()], color);
+ if (draw_right_side)
+ set_pixel_with_draw_op(bits[rect.right()], color);
+ }
+ }
+}
+
+void Painter::draw_bitmap(const IntPoint& p, const CharacterBitmap& bitmap, Color color)
+{
+ auto rect = IntRect(p, bitmap.size()).translated(translation());
+ auto clipped_rect = rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - rect.top();
+ const int last_row = clipped_rect.bottom() - rect.top();
+ const int first_column = clipped_rect.left() - rect.left();
+ const int last_column = clipped_rect.right() - rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const char* bitmap_row = &bitmap.bits()[first_row * bitmap.width() + first_column];
+ const size_t bitmap_skip = bitmap.width();
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int j = 0; j <= (last_column - first_column); ++j) {
+ char fc = bitmap_row[j];
+ if (fc == '#')
+ dst[j] = color.value();
+ }
+ bitmap_row += bitmap_skip;
+ dst += dst_skip;
+ }
+}
+
+void Painter::draw_bitmap(const IntPoint& p, const GlyphBitmap& bitmap, Color color)
+{
+ auto dst_rect = IntRect(p, bitmap.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int j = 0; j <= (last_column - first_column); ++j) {
+ if (bitmap.bit_at(j + first_column, row))
+ dst[j] = color.value();
+ }
+ dst += dst_skip;
+ }
+}
+
+void Painter::draw_triangle(const IntPoint& a, const IntPoint& b, const IntPoint& c, Color color)
+{
+ RGBA32 rgba = color.value();
+
+ IntPoint p0(a);
+ IntPoint p1(b);
+ IntPoint p2(c);
+
+ if (p0.y() > p1.y())
+ swap(p0, p1);
+ if (p0.y() > p2.y())
+ swap(p0, p2);
+ if (p1.y() > p2.y())
+ swap(p1, p2);
+
+ auto clip = clip_rect();
+ if (p0.y() >= clip.bottom())
+ return;
+ if (p2.y() < clip.top())
+ return;
+
+ float dx01 = (float)(p1.x() - p0.x()) / (p1.y() - p0.y());
+ float dx02 = (float)(p2.x() - p0.x()) / (p2.y() - p0.y());
+ float dx12 = (float)(p2.x() - p1.x()) / (p2.y() - p1.y());
+
+ float x01 = p0.x();
+ float x02 = p0.x();
+
+ int top = p0.y();
+ if (top < clip.top()) {
+ x01 += dx01 * (clip.top() - top);
+ x02 += dx02 * (clip.top() - top);
+ top = clip.top();
+ }
+
+ for (int y = top; y < p1.y() && y < clip.bottom(); ++y) {
+ int start = x01 > x02 ? max((int)x02, clip.left()) : max((int)x01, clip.left());
+ int end = x01 > x02 ? min((int)x01, clip.right()) : min((int)x02, clip.right());
+ auto* scanline = m_target->scanline(y);
+ for (int x = start; x < end; x++) {
+ scanline[x] = rgba;
+ }
+ x01 += dx01;
+ x02 += dx02;
+ }
+
+ x02 = p0.x() + dx02 * (p1.y() - p0.y());
+ float x12 = p1.x();
+
+ top = p1.y();
+ if (top < clip.top()) {
+ x02 += dx02 * (clip.top() - top);
+ x12 += dx12 * (clip.top() - top);
+ top = clip.top();
+ }
+
+ for (int y = top; y < p2.y() && y < clip.bottom(); ++y) {
+ int start = x12 > x02 ? max((int)x02, clip.left()) : max((int)x12, clip.left());
+ int end = x12 > x02 ? min((int)x12, clip.right()) : min((int)x02, clip.right());
+ auto* scanline = m_target->scanline(y);
+ for (int x = start; x < end; x++) {
+ scanline[x] = rgba;
+ }
+ x02 += dx02;
+ x12 += dx12;
+ }
+}
+
+void Painter::blit_scaled(const IntRect& dst_rect_raw, const Gfx::Bitmap& source, const IntRect& src_rect, float hscale, float vscale)
+{
+ auto dst_rect = IntRect(dst_rect_raw.location(), dst_rect_raw.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = (clipped_rect.top() - dst_rect.top());
+ const int last_row = (clipped_rect.bottom() - dst_rect.top());
+ const int first_column = (clipped_rect.left() - dst_rect.left());
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ int x_start = first_column + src_rect.left();
+ for (int row = first_row; row <= last_row; ++row) {
+ int sr = (row + src_rect.top()) * vscale;
+ if (sr >= source.size().height() || sr < 0) {
+ dst += dst_skip;
+ continue;
+ }
+ const RGBA32* sl = source.scanline(sr);
+ for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
+ int sx = x * hscale;
+ if (sx < source.size().width() && sx >= 0)
+ dst[x - x_start] = sl[sx];
+ }
+ dst += dst_skip;
+ }
+ return;
+}
+
+void Painter::blit_with_opacity(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect, float opacity)
+{
+ ASSERT(!m_target->has_alpha_channel());
+
+ if (!opacity)
+ return;
+ if (opacity >= 1.0f)
+ return blit(position, source, src_rect);
+
+ u8 alpha = 255 * opacity;
+
+ IntRect safe_src_rect = IntRect::intersection(src_rect, source.rect());
+ IntRect dst_rect(position, safe_src_rect.size());
+ dst_rect.move_by(state().translation);
+ auto clipped_rect = IntRect::intersection(dst_rect, clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const unsigned src_skip = source.pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int x = 0; x <= (last_column - first_column); ++x) {
+ Color src_color_with_alpha = Color::from_rgb(src[x]);
+ src_color_with_alpha.set_alpha(alpha);
+ Color dst_color = Color::from_rgb(dst[x]);
+ dst[x] = dst_color.blend(src_color_with_alpha).value();
+ }
+ dst += dst_skip;
+ src += src_skip;
+ }
+}
+
+void Painter::blit_filtered(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect, Function<Color(Color)> filter)
+{
+ IntRect safe_src_rect = src_rect.intersected(source.rect());
+ auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const size_t src_skip = source.pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int x = 0; x <= (last_column - first_column); ++x) {
+ u8 alpha = Color::from_rgba(src[x]).alpha();
+ if (alpha == 0xff)
+ dst[x] = filter(Color::from_rgba(src[x])).value();
+ else if (!alpha)
+ continue;
+ else
+ dst[x] = Color::from_rgba(dst[x]).blend(filter(Color::from_rgba(src[x]))).value();
+ }
+ dst += dst_skip;
+ src += src_skip;
+ }
+}
+
+void Painter::blit_brightened(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect)
+{
+ return blit_filtered(position, source, src_rect, [](Color src) {
+ return src.lightened();
+ });
+}
+
+void Painter::blit_dimmed(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect)
+{
+ return blit_filtered(position, source, src_rect, [](Color src) {
+ return src.to_grayscale().lightened();
+ });
+}
+
+void Painter::draw_tiled_bitmap(const IntRect& a_dst_rect, const Gfx::Bitmap& source)
+{
+ auto dst_rect = a_dst_rect.translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = (clipped_rect.top() - dst_rect.top());
+ const int last_row = (clipped_rect.bottom() - dst_rect.top());
+ const int first_column = (clipped_rect.left() - dst_rect.left());
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) {
+ int x_start = first_column + a_dst_rect.left();
+ for (int row = first_row; row <= last_row; ++row) {
+ const RGBA32* sl = source.scanline((row + a_dst_rect.top())
+ % source.size().height());
+ for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
+ dst[x - x_start] = sl[x % source.size().width()];
+ }
+ dst += dst_skip;
+ }
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void Painter::blit_offset(const IntPoint& position,
+ const Gfx::Bitmap& source,
+ const IntRect& src_rect,
+ const IntPoint& offset)
+{
+ auto dst_rect = IntRect(position, src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = (clipped_rect.top() - dst_rect.top());
+ const int last_row = (clipped_rect.bottom() - dst_rect.top());
+ const int first_column = (clipped_rect.left() - dst_rect.left());
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) {
+ int x_start = first_column + src_rect.left();
+ for (int row = first_row; row <= last_row; ++row) {
+ int sr = row - offset.y() + src_rect.top();
+ if (sr >= source.size().height() || sr < 0) {
+ dst += dst_skip;
+ continue;
+ }
+ const RGBA32* sl = source.scanline(sr);
+ for (int x = x_start; x < clipped_rect.width() + x_start; ++x) {
+ int sx = x - offset.x();
+ if (sx < source.size().width() && sx >= 0)
+ dst[x - x_start] = sl[sx];
+ }
+ dst += dst_skip;
+ }
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void Painter::blit_with_alpha(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect)
+{
+ ASSERT(source.has_alpha_channel());
+ IntRect safe_src_rect = src_rect.intersected(source.rect());
+ auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ const int last_column = clipped_rect.right() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+ const size_t src_skip = source.pitch() / sizeof(RGBA32);
+
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int x = 0; x <= (last_column - first_column); ++x) {
+ u8 alpha = Color::from_rgba(src[x]).alpha();
+ if (alpha == 0xff)
+ dst[x] = src[x];
+ else if (!alpha)
+ continue;
+ else
+ dst[x] = Color::from_rgba(dst[x]).blend(Color::from_rgba(src[x])).value();
+ }
+ dst += dst_skip;
+ src += src_skip;
+ }
+}
+
+void Painter::blit(const IntPoint& position, const Gfx::Bitmap& source, const IntRect& src_rect, float opacity)
+{
+ if (opacity < 1.0f)
+ return blit_with_opacity(position, source, src_rect, opacity);
+ if (source.has_alpha_channel())
+ return blit_with_alpha(position, source, src_rect);
+ auto safe_src_rect = src_rect.intersected(source.rect());
+ ASSERT(source.rect().contains(safe_src_rect));
+ auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+ const int first_row = clipped_rect.top() - dst_rect.top();
+ const int last_row = clipped_rect.bottom() - dst_rect.top();
+ const int first_column = clipped_rect.left() - dst_rect.left();
+ RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
+ const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
+
+ if (source.format() == BitmapFormat::RGB32 || source.format() == BitmapFormat::RGBA32) {
+ const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t src_skip = source.pitch() / sizeof(RGBA32);
+ for (int row = first_row; row <= last_row; ++row) {
+ fast_u32_copy(dst, src, clipped_rect.width());
+ dst += dst_skip;
+ src += src_skip;
+ }
+ return;
+ }
+
+ if (Bitmap::is_indexed(source.format())) {
+ const u8* src = source.scanline_u8(src_rect.top() + first_row) + src_rect.left() + first_column;
+ const size_t src_skip = source.pitch();
+ for (int row = first_row; row <= last_row; ++row) {
+ for (int i = 0; i < clipped_rect.width(); ++i)
+ dst[i] = source.palette_color(src[i]).value();
+ dst += dst_skip;
+ src += src_skip;
+ }
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+template<bool has_alpha_channel, typename GetPixel>
+ALWAYS_INLINE static void do_draw_integer_scaled_bitmap(Gfx::Bitmap& target, const IntRect& dst_rect, const Gfx::Bitmap& source, int hfactor, int vfactor, GetPixel get_pixel, float opacity)
+{
+ bool has_opacity = opacity != 1.0f;
+ for (int y = source.rect().top(); y <= source.rect().bottom(); ++y) {
+ int dst_y = dst_rect.y() + y * vfactor;
+ for (int x = source.rect().left(); x <= source.rect().right(); ++x) {
+ auto src_pixel = get_pixel(source, x, y);
+ if (has_opacity)
+ src_pixel.set_alpha(src_pixel.alpha() * opacity);
+ for (int yo = 0; yo < vfactor; ++yo) {
+ auto* scanline = (Color*)target.scanline(dst_y + yo);
+ int dst_x = dst_rect.x() + x * hfactor;
+ for (int xo = 0; xo < hfactor; ++xo) {
+ if constexpr (has_alpha_channel)
+ scanline[dst_x + xo] = scanline[dst_x + xo].blend(src_pixel);
+ else
+ scanline[dst_x + xo] = src_pixel;
+ }
+ }
+ }
+ }
+}
+
+template<bool has_alpha_channel, typename GetPixel>
+ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, const IntRect& dst_rect, const IntRect& clipped_rect, const Gfx::Bitmap& source, const IntRect& src_rect, int hscale, int vscale, GetPixel get_pixel, float opacity)
+{
+ if (dst_rect == clipped_rect && !(dst_rect.width() % src_rect.width()) && !(dst_rect.height() % src_rect.height())) {
+ int hfactor = dst_rect.width() / src_rect.width();
+ int vfactor = dst_rect.height() / src_rect.height();
+ if (hfactor == 2 && vfactor == 2)
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 2, 2, get_pixel, opacity);
+ if (hfactor == 3 && vfactor == 3)
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 3, 3, get_pixel, opacity);
+ if (hfactor == 4 && vfactor == 4)
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, 4, 4, get_pixel, opacity);
+ return do_draw_integer_scaled_bitmap<has_alpha_channel>(target, dst_rect, source, hfactor, vfactor, get_pixel, opacity);
+ }
+
+ bool has_opacity = opacity != 1.0f;
+
+ for (int y = clipped_rect.top(); y <= clipped_rect.bottom(); ++y) {
+ auto* scanline = (Color*)target.scanline(y);
+ for (int x = clipped_rect.left(); x <= clipped_rect.right(); ++x) {
+ auto scaled_x = ((x - dst_rect.x()) * hscale) >> 16;
+ auto scaled_y = ((y - dst_rect.y()) * vscale) >> 16;
+ auto src_pixel = get_pixel(source, scaled_x, scaled_y);
+ if (has_opacity)
+ src_pixel.set_alpha(src_pixel.alpha() * opacity);
+ if constexpr (has_alpha_channel) {
+ scanline[x] = scanline[x].blend(src_pixel);
+ } else
+ scanline[x] = src_pixel;
+ }
+ }
+}
+
+void Painter::draw_scaled_bitmap(const IntRect& a_dst_rect, const Gfx::Bitmap& source, const IntRect& src_rect, float opacity)
+{
+ auto dst_rect = a_dst_rect;
+ if (dst_rect.size() == src_rect.size())
+ return blit(dst_rect.location(), source, src_rect, opacity);
+
+ auto safe_src_rect = src_rect.intersected(source.rect());
+ ASSERT(source.rect().contains(safe_src_rect));
+ dst_rect.move_by(state().translation);
+ auto clipped_rect = dst_rect.intersected(clip_rect());
+ if (clipped_rect.is_empty())
+ return;
+
+ int hscale = (src_rect.width() << 16) / dst_rect.width();
+ int vscale = (src_rect.height() << 16) / dst_rect.height();
+
+ if (source.has_alpha_channel()) {
+ switch (source.format()) {
+ case BitmapFormat::RGB32:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGB32>, opacity);
+ break;
+ case BitmapFormat::RGBA32:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGBA32>, opacity);
+ break;
+ case BitmapFormat::Indexed8:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed8>, opacity);
+ break;
+ case BitmapFormat::Indexed4:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed4>, opacity);
+ break;
+ case BitmapFormat::Indexed2:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed2>, opacity);
+ break;
+ case BitmapFormat::Indexed1:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed1>, opacity);
+ break;
+ default:
+ do_draw_scaled_bitmap<true>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Invalid>, opacity);
+ break;
+ }
+ } else {
+ switch (source.format()) {
+ case BitmapFormat::RGB32:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGB32>, opacity);
+ break;
+ case BitmapFormat::RGBA32:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::RGBA32>, opacity);
+ break;
+ case BitmapFormat::Indexed8:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Indexed8>, opacity);
+ break;
+ default:
+ do_draw_scaled_bitmap<false>(*m_target, dst_rect, clipped_rect, source, src_rect, hscale, vscale, get_pixel<BitmapFormat::Invalid>, opacity);
+ break;
+ }
+ }
+}
+
+FLATTEN void Painter::draw_glyph(const IntPoint& point, u32 code_point, Color color)
+{
+ draw_glyph(point, code_point, font(), color);
+}
+
+FLATTEN void Painter::draw_glyph(const IntPoint& point, u32 code_point, const Font& font, Color color)
+{
+ draw_bitmap(point, font.glyph_bitmap(code_point), color);
+}
+
+void Painter::draw_emoji(const IntPoint& point, const Gfx::Bitmap& emoji, const Font& font)
+{
+ if (!font.is_fixed_width())
+ blit(point, emoji, emoji.rect());
+ else {
+ IntRect dst_rect {
+ point.x(),
+ point.y(),
+ font.glyph_width('x'),
+ font.glyph_height()
+ };
+ draw_scaled_bitmap(dst_rect, emoji, emoji.rect());
+ }
+}
+
+void Painter::draw_glyph_or_emoji(const IntPoint& point, u32 code_point, const Font& font, Color color)
+{
+ if (code_point < (u32)font.glyph_count()) {
+ // This looks like a regular character.
+ draw_glyph(point, (size_t)code_point, font, color);
+ return;
+ }
+
+ // Perhaps it's an emoji?
+ auto* emoji = Emoji::emoji_for_code_point(code_point);
+ if (emoji == nullptr) {
+#ifdef EMOJI_DEBUG
+ dbg() << "Failed to find an emoji for code_point " << code_point;
+#endif
+ draw_glyph(point, '?', font, color);
+ return;
+ }
+
+ draw_emoji(point, *emoji, font);
+}
+
+static void apply_elision(Utf8View& final_text, String& elided_text, size_t offset)
+{
+ StringBuilder builder;
+ builder.append(final_text.substring_view(0, offset).as_string());
+ builder.append("...");
+ elided_text = builder.to_string();
+ final_text = Utf8View { elided_text };
+}
+
+static void apply_elision(Utf32View& final_text, Vector<u32>& elided_text, size_t offset)
+{
+ elided_text.append(final_text.code_points(), offset);
+ elided_text.append('.');
+ elided_text.append('.');
+ elided_text.append('.');
+ final_text = Utf32View { elided_text.data(), elided_text.size() };
+}
+
+template<typename TextType>
+struct ElidedText {
+};
+
+template<>
+struct ElidedText<Utf8View> {
+ typedef String Type;
+};
+
+template<>
+struct ElidedText<Utf32View> {
+ typedef Vector<u32> Type;
+};
+
+template<typename TextType, typename DrawGlyphFunction>
+void draw_text_line(const IntRect& a_rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph)
+{
+ auto rect = a_rect;
+ TextType final_text(text);
+ typename ElidedText<TextType>::Type elided_text;
+ if (elision == TextElision::Right) {
+ int text_width = font.width(final_text);
+ if (font.width(final_text) > rect.width()) {
+ int glyph_spacing = font.glyph_spacing();
+ int new_width = font.width("...");
+ if (new_width < text_width) {
+ size_t offset = 0;
+ for (auto it = text.begin(); it != text.end(); ++it) {
+ auto code_point = *it;
+ int glyph_width = font.glyph_or_emoji_width(code_point);
+ // NOTE: Glyph spacing should not be added after the last glyph on the line,
+ // but since we are here because the last glyph does not actually fit on the line,
+ // we don't have to worry about spacing.
+ int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing;
+ if (width_with_this_glyph_included > rect.width())
+ break;
+ new_width += glyph_width + glyph_spacing;
+ offset = text.iterator_offset(it);
+ }
+ apply_elision(final_text, elided_text, offset);
+ }
+ }
+ }
+
+ switch (alignment) {
+ case TextAlignment::TopLeft:
+ case TextAlignment::CenterLeft:
+ break;
+ case TextAlignment::TopRight:
+ case TextAlignment::CenterRight:
+ case TextAlignment::BottomRight:
+ rect.set_x(rect.right() - font.width(final_text));
+ break;
+ case TextAlignment::Center: {
+ auto shrunken_rect = rect;
+ shrunken_rect.set_width(font.width(final_text));
+ shrunken_rect.center_within(rect);
+ rect = shrunken_rect;
+ break;
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ if (is_vertically_centered_text_alignment(alignment)) {
+ int distance_from_baseline_to_bottom = (font.glyph_height() - 1) - font.baseline();
+ rect.move_by(0, distance_from_baseline_to_bottom / 2);
+ }
+
+ auto point = rect.location();
+ int space_width = font.glyph_width(' ') + font.glyph_spacing();
+
+ for (u32 code_point : final_text) {
+ if (code_point == ' ') {
+ point.move_by(space_width, 0);
+ continue;
+ }
+ IntSize glyph_size(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), font.glyph_height());
+ draw_glyph({ point, glyph_size }, code_point);
+ point.move_by(glyph_size.width(), 0);
+ }
+}
+
+static inline size_t draw_text_iterator_offset(const Utf8View& text, const Utf8View::Iterator& it)
+{
+ return text.byte_offset_of(it);
+}
+
+static inline size_t draw_text_iterator_offset(const Utf32View& text, const Utf32View::Iterator& it)
+{
+ return it - text.begin();
+}
+
+static inline size_t draw_text_get_length(const Utf8View& text)
+{
+ return text.byte_length();
+}
+
+static inline size_t draw_text_get_length(const Utf32View& text)
+{
+ return text.length();
+}
+
+template<typename TextType, typename DrawGlyphFunction>
+void do_draw_text(const IntRect& rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph)
+{
+ Vector<TextType, 32> lines;
+
+ size_t start_of_current_line = 0;
+ for (auto it = text.begin(); it != text.end(); ++it) {
+ u32 code_point = *it;
+ if (code_point == '\n') {
+ auto offset = draw_text_iterator_offset(text, it);
+ TextType line = text.substring_view(start_of_current_line, offset - start_of_current_line);
+ lines.append(line);
+ start_of_current_line = offset + 1;
+ }
+ }
+
+ if (start_of_current_line != draw_text_get_length(text)) {
+ TextType line = text.substring_view(start_of_current_line, draw_text_get_length(text) - start_of_current_line);
+ lines.append(line);
+ }
+
+ static const int line_spacing = 4;
+ int line_height = font.glyph_height() + line_spacing;
+ IntRect bounding_rect { 0, 0, 0, (static_cast<int>(lines.size()) * line_height) - line_spacing };
+
+ for (auto& line : lines) {
+ auto line_width = font.width(line);
+ if (line_width > bounding_rect.width())
+ bounding_rect.set_width(line_width);
+ }
+
+ switch (alignment) {
+ case TextAlignment::TopLeft:
+ bounding_rect.set_location(rect.location());
+ break;
+ case TextAlignment::TopRight:
+ bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() });
+ break;
+ case TextAlignment::CenterLeft:
+ bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) });
+ break;
+ case TextAlignment::CenterRight:
+ bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) });
+ break;
+ case TextAlignment::Center:
+ bounding_rect.center_within(rect);
+ break;
+ case TextAlignment::BottomRight:
+ bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), (rect.bottom() + 1) - bounding_rect.height() });
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ for (size_t i = 0; i < lines.size(); ++i) {
+ auto& line = lines[i];
+ IntRect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast<int>(i) * line_height, bounding_rect.width(), line_height };
+ line_rect.intersect(rect);
+ draw_text_line(line_rect, line, font, alignment, elision, draw_glyph);
+ }
+}
+
+void Painter::draw_text(const IntRect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision)
+{
+ draw_text(rect, text, font(), alignment, color, elision);
+}
+
+void Painter::draw_text(const IntRect& rect, const Utf32View& text, TextAlignment alignment, Color color, TextElision elision)
+{
+ draw_text(rect, text, font(), alignment, color, elision);
+}
+
+void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
+{
+ Utf8View text { raw_text };
+ do_draw_text(rect, Utf8View(text), font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_glyph_or_emoji(r.location(), code_point, font, color);
+ });
+}
+
+void Painter::draw_text(const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
+{
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_glyph_or_emoji(r.location(), code_point, font, color);
+ });
+}
+
+void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, TextElision elision)
+{
+ Utf8View text { raw_text };
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_one_glyph(r, code_point);
+ });
+}
+
+void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf8View& text, const Font& font, TextAlignment alignment, TextElision elision)
+{
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_one_glyph(r, code_point);
+ });
+}
+
+void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, TextElision elision)
+{
+ do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
+ draw_one_glyph(r, code_point);
+ });
+}
+
+void Painter::set_pixel(const IntPoint& p, Color color)
+{
+ auto point = p;
+ point.move_by(state().translation);
+ if (!clip_rect().contains(point))
+ return;
+ m_target->scanline(point.y())[point.x()] = color.value();
+}
+
+ALWAYS_INLINE void Painter::set_pixel_with_draw_op(u32& pixel, const Color& color)
+{
+ switch (draw_op()) {
+ case DrawOp::Copy:
+ pixel = color.value();
+ break;
+ case DrawOp::Xor:
+ pixel = color.xored(Color::from_rgba(pixel)).value();
+ break;
+ case DrawOp::Invert:
+ pixel = Color::from_rgba(pixel).inverted().value();
+ break;
+ }
+}
+
+ALWAYS_INLINE void Painter::fill_scanline_with_draw_op(int y, int x, int width, const Color& color)
+{
+ switch (draw_op()) {
+ case DrawOp::Copy:
+ fast_u32_fill(m_target->scanline(y) + x, color.value(), width);
+ break;
+ case DrawOp::Xor: {
+ auto* pixel = m_target->scanline(y) + x;
+ auto* end = pixel + width;
+ while (pixel < end) {
+ *pixel = Color::from_rgba(*pixel).xored(color).value();
+ pixel++;
+ }
+ break;
+ }
+ case DrawOp::Invert: {
+ auto* pixel = m_target->scanline(y) + x;
+ auto* end = pixel + width;
+ while (pixel < end) {
+ *pixel = Color::from_rgba(*pixel).inverted().value();
+ pixel++;
+ }
+ break;
+ }
+ }
+}
+
+void Painter::draw_pixel(const IntPoint& position, Color color, int thickness)
+{
+ if (thickness == 1)
+ return set_pixel_with_draw_op(m_target->scanline(position.y())[position.x()], color);
+ IntRect rect { position.translated(-(thickness / 2), -(thickness / 2)), { thickness, thickness } };
+ fill_rect(rect.translated(-state().translation), color);
+}
+
+void Painter::draw_line(const IntPoint& p1, const IntPoint& p2, Color color, int thickness, LineStyle style)
+{
+ if (color.alpha() == 0)
+ return;
+
+ auto clip_rect = this->clip_rect();
+
+ auto point1 = p1;
+ point1.move_by(state().translation);
+
+ auto point2 = p2;
+ point2.move_by(state().translation);
+
+ // Special case: vertical line.
+ if (point1.x() == point2.x()) {
+ const int x = point1.x();
+ if (x < clip_rect.left() || x > clip_rect.right())
+ return;
+ if (point1.y() > point2.y())
+ swap(point1, point2);
+ if (point1.y() > clip_rect.bottom())
+ return;
+ if (point2.y() < clip_rect.top())
+ return;
+ int min_y = max(point1.y(), clip_rect.top());
+ int max_y = min(point2.y(), clip_rect.bottom());
+ if (style == LineStyle::Dotted) {
+ for (int y = min_y; y <= max_y; y += thickness * 2)
+ draw_pixel({ x, y }, color, thickness);
+ } else if (style == LineStyle::Dashed) {
+ for (int y = min_y; y <= max_y; y += thickness * 6) {
+ draw_pixel({ x, y }, color, thickness);
+ draw_pixel({ x, min(y + thickness, max_y) }, color, thickness);
+ draw_pixel({ x, min(y + thickness * 2, max_y) }, color, thickness);
+ }
+ } else {
+ for (int y = min_y; y <= max_y; ++y)
+ draw_pixel({ x, y }, color, thickness);
+ }
+ return;
+ }
+
+ // Special case: horizontal line.
+ if (point1.y() == point2.y()) {
+ const int y = point1.y();
+ if (y < clip_rect.top() || y > clip_rect.bottom())
+ return;
+ if (point1.x() > point2.x())
+ swap(point1, point2);
+ if (point1.x() > clip_rect.right())
+ return;
+ if (point2.x() < clip_rect.left())
+ return;
+ int min_x = max(point1.x(), clip_rect.left());
+ int max_x = min(point2.x(), clip_rect.right());
+ if (style == LineStyle::Dotted) {
+ for (int x = min_x; x <= max_x; x += thickness * 2)
+ draw_pixel({ x, y }, color, thickness);
+ } else if (style == LineStyle::Dashed) {
+ for (int x = min_x; x <= max_x; x += thickness * 6) {
+ draw_pixel({ x, y }, color, thickness);
+ draw_pixel({ min(x + thickness, max_x), y }, color, thickness);
+ draw_pixel({ min(x + thickness * 2, max_x), y }, color, thickness);
+ }
+ } else {
+ for (int x = min_x; x <= max_x; ++x)
+ draw_pixel({ x, y }, color, thickness);
+ }
+ return;
+ }
+
+ // FIXME: Implement dotted/dashed diagonal lines.
+ ASSERT(style == LineStyle::Solid);
+
+ const double adx = abs(point2.x() - point1.x());
+ const double ady = abs(point2.y() - point1.y());
+
+ if (adx > ady) {
+ if (point1.x() > point2.x())
+ swap(point1, point2);
+ } else {
+ if (point1.y() > point2.y())
+ swap(point1, point2);
+ }
+
+ // FIXME: Implement clipping below.
+ const double dx = point2.x() - point1.x();
+ const double dy = point2.y() - point1.y();
+ double error = 0;
+
+ if (dx > dy) {
+ const double y_step = dy == 0 ? 0 : (dy > 0 ? 1 : -1);
+ const double delta_error = fabs(dy / dx);
+ int y = point1.y();
+ for (int x = point1.x(); x <= point2.x(); ++x) {
+ if (clip_rect.contains(x, y))
+ draw_pixel({ x, y }, color, thickness);
+ error += delta_error;
+ if (error >= 0.5) {
+ y = (double)y + y_step;
+ error -= 1.0;
+ }
+ }
+ } else {
+ const double x_step = dx == 0 ? 0 : (dx > 0 ? 1 : -1);
+ const double delta_error = fabs(dx / dy);
+ int x = point1.x();
+ for (int y = point1.y(); y <= point2.y(); ++y) {
+ if (clip_rect.contains(x, y))
+ draw_pixel({ x, y }, color, thickness);
+ error += delta_error;
+ if (error >= 0.5) {
+ x = (double)x + x_step;
+ error -= 1.0;
+ }
+ }
+ }
+}
+
+static void split_quadratic_bezier_curve(const FloatPoint& original_control, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ auto po1_midpoint = original_control + p1;
+ po1_midpoint /= 2;
+
+ auto po2_midpoint = original_control + p2;
+ po2_midpoint /= 2;
+
+ auto new_segment = po1_midpoint + po2_midpoint;
+ new_segment /= 2;
+
+ Painter::for_each_line_segment_on_bezier_curve(po1_midpoint, p1, new_segment, callback);
+ Painter::for_each_line_segment_on_bezier_curve(po2_midpoint, new_segment, p2, callback);
+}
+
+static bool can_approximate_bezier_curve(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& control)
+{
+ constexpr static int tolerance = 15;
+
+ auto p1x = 3 * control.x() - 2 * p1.x() - p2.x();
+ auto p1y = 3 * control.y() - 2 * p1.y() - p2.y();
+ auto p2x = 3 * control.x() - 2 * p2.x() - p1.x();
+ auto p2y = 3 * control.y() - 2 * p2.y() - p1.y();
+
+ p1x = p1x * p1x;
+ p1y = p1y * p1y;
+ p2x = p2x * p2x;
+ p2y = p2y * p2y;
+
+ return max(p1x, p2x) + max(p1y, p2y) <= tolerance;
+}
+
+void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ if (can_approximate_bezier_curve(p1, p2, control_point)) {
+ callback(p1, p2);
+ } else {
+ split_quadratic_bezier_curve(control_point, p1, p2, callback);
+ }
+}
+
+void Painter::for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&& callback)
+{
+ for_each_line_segment_on_bezier_curve(control_point, p1, p2, callback);
+}
+
+static void split_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ auto half_theta_delta = theta_delta / 2;
+ auto theta_mid = theta_1 + half_theta_delta;
+
+ auto xc = cosf(x_axis_rotation);
+ auto xs = sinf(x_axis_rotation);
+ auto tc = cosf(theta_1 + half_theta_delta);
+ auto ts = sinf(theta_1 + half_theta_delta);
+
+ auto x2 = xc * radii.x() * tc - xs * radii.y() * ts + center.x();
+ auto y2 = xs * radii.x() * tc + xc * radii.y() * ts + center.y();
+
+ FloatPoint mid_point = { x2, y2 };
+
+ Painter::for_each_line_segment_on_elliptical_arc(p1, mid_point, center, radii, x_axis_rotation, theta_1, half_theta_delta, callback);
+ Painter::for_each_line_segment_on_elliptical_arc(mid_point, p2, center, radii, x_axis_rotation, theta_mid, half_theta_delta, callback);
+}
+
+static bool can_approximate_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta)
+{
+ constexpr static float tolerance = 1;
+
+ auto half_theta_delta = theta_delta / 2.0f;
+
+ auto xc = cosf(x_axis_rotation);
+ auto xs = sinf(x_axis_rotation);
+ auto tc = cosf(theta_1 + half_theta_delta);
+ auto ts = sinf(theta_1 + half_theta_delta);
+
+ auto x2 = xc * radii.x() * tc - xs * radii.y() * ts + center.x();
+ auto y2 = xs * radii.x() * tc + xc * radii.y() * ts + center.y();
+
+ auto ellipse_mid_point = FloatPoint { x2, y2 };
+ auto line_mid_point = p1 + (p2 - p1) / 2.0f;
+
+ return ellipse_mid_point.distance_from(line_mid_point) < tolerance;
+}
+
+void Painter::draw_quadratic_bezier_curve(const IntPoint& control_point, const IntPoint& p1, const IntPoint& p2, Color color, int thickness, LineStyle style)
+{
+ for_each_line_segment_on_bezier_curve(FloatPoint(control_point), FloatPoint(p1), FloatPoint(p2), [&](const FloatPoint& fp1, const FloatPoint& fp2) {
+ draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
+ });
+}
+
+void Painter::for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>& callback)
+{
+ if (can_approximate_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta)) {
+ callback(p1, p2);
+ } else {
+ split_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta, callback);
+ }
+}
+
+void Painter::for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>&& callback)
+{
+ for_each_line_segment_on_elliptical_arc(p1, p2, center, radii, x_axis_rotation, theta_1, theta_delta, callback);
+}
+
+void Painter::draw_elliptical_arc(const IntPoint& p1, const IntPoint& p2, const IntPoint& center, const FloatPoint& radii, float x_axis_rotation, float theta_1, float theta_delta, Color color, int thickness, LineStyle style)
+{
+ for_each_line_segment_on_elliptical_arc(FloatPoint(p1), FloatPoint(p2), FloatPoint(center), radii, x_axis_rotation, theta_1, theta_delta, [&](const FloatPoint& fp1, const FloatPoint& fp2) {
+ draw_line(IntPoint(fp1.x(), fp1.y()), IntPoint(fp2.x(), fp2.y()), color, thickness, style);
+ });
+}
+
+void Painter::add_clip_rect(const IntRect& rect)
+{
+ state().clip_rect.intersect(rect.translated(translation()));
+ state().clip_rect.intersect(m_target->rect());
+}
+
+void Painter::clear_clip_rect()
+{
+ state().clip_rect = m_clip_origin;
+}
+
+PainterStateSaver::PainterStateSaver(Painter& painter)
+ : m_painter(painter)
+{
+ m_painter.save();
+}
+
+PainterStateSaver::~PainterStateSaver()
+{
+ m_painter.restore();
+}
+
+void Painter::stroke_path(const Path& path, Color color, int thickness)
+{
+ FloatPoint cursor;
+
+ for (auto& segment : path.segments()) {
+ switch (segment.type()) {
+ case Segment::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ break;
+ case Segment::Type::MoveTo:
+ cursor = segment.point();
+ break;
+ case Segment::Type::LineTo:
+ draw_line(cursor.to_type<int>(), segment.point().to_type<int>(), color, thickness);
+ cursor = segment.point();
+ break;
+ case Segment::Type::QuadraticBezierCurveTo: {
+ auto& through = static_cast<const QuadraticBezierCurveSegment&>(segment).through();
+ draw_quadratic_bezier_curve(through.to_type<int>(), cursor.to_type<int>(), segment.point().to_type<int>(), color, thickness);
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::EllipticalArcTo:
+ auto& arc = static_cast<const EllipticalArcSegment&>(segment);
+ draw_elliptical_arc(cursor.to_type<int>(), segment.point().to_type<int>(), arc.center().to_type<int>(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), color, thickness);
+ cursor = segment.point();
+ break;
+ }
+ }
+}
+
+//#define FILL_PATH_DEBUG
+
+[[maybe_unused]] static void approximately_place_on_int_grid(FloatPoint ffrom, FloatPoint fto, IntPoint& from, IntPoint& to, Optional<IntPoint> previous_to)
+{
+ auto diffs = fto - ffrom;
+ // Truncate all first (round down).
+ from = ffrom.to_type<int>();
+ to = fto.to_type<int>();
+ // There are 16 possible configurations, by deciding to round each
+ // coord up or down (and there are four coords, from.x from.y to.x to.y)
+ // we will simply choose one which most closely matches the correct slope
+ // with the following heuristic:
+ // - if the x diff is positive or zero (that is, a right-to-left slant), round 'from.x' up and 'to.x' down.
+ // - if the x diff is negative (that is, a left-to-right slant), round 'from.x' down and 'to.x' up.
+ // Note that we do not need to touch the 'y' attribute, as that is our scanline.
+ if (diffs.x() >= 0) {
+ from.set_x(from.x() + 1);
+ } else {
+ to.set_x(to.x() + 1);
+ }
+ if (previous_to.has_value() && from.x() != previous_to.value().x()) // The points have to line up, since we're using these lines to fill a shape.
+ from.set_x(previous_to.value().x());
+}
+
+void Painter::fill_path(Path& path, Color color, WindingRule winding_rule)
+{
+ const auto& segments = path.split_lines();
+
+ if (segments.size() == 0)
+ return;
+
+ Vector<Path::SplitLineSegment> active_list;
+ active_list.ensure_capacity(segments.size());
+
+ // first, grab the segments for the very first scanline
+ int first_y = path.bounding_box().bottom_right().y() + 1;
+ int last_y = path.bounding_box().top_left().y() - 1;
+ float scanline = first_y;
+
+ size_t last_active_segment { 0 };
+
+ for (auto& segment : segments) {
+ if (segment.maximum_y != scanline)
+ break;
+ active_list.append(segment);
+ ++last_active_segment;
+ }
+
+ auto is_inside_shape = [winding_rule](int winding_number) {
+ if (winding_rule == WindingRule::Nonzero)
+ return winding_number != 0;
+
+ if (winding_rule == WindingRule::EvenOdd)
+ return winding_number % 2 == 0;
+
+ ASSERT_NOT_REACHED();
+ };
+
+ auto increment_winding = [winding_rule](int& winding_number, const IntPoint& from, const IntPoint& to) {
+ if (winding_rule == WindingRule::EvenOdd) {
+ ++winding_number;
+ return;
+ }
+
+ if (winding_rule == WindingRule::Nonzero) {
+ if (from.dy_relative_to(to) < 0)
+ ++winding_number;
+ else
+ --winding_number;
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+ };
+
+ while (scanline >= last_y) {
+ Optional<IntPoint> previous_to;
+ if (active_list.size()) {
+ // sort the active list by 'x' from right to left
+ quick_sort(active_list, [](const auto& line0, const auto& line1) {
+ return line1.x < line0.x;
+ });
+#ifdef FILL_PATH_DEBUG
+ if ((int)scanline % 10 == 0) {
+ draw_text(IntRect(active_list.last().x - 20, scanline, 20, 10), String::number((int)scanline));
+ }
+#endif
+
+ if (active_list.size() > 1) {
+ auto winding_number { 0 };
+ for (size_t i = 1; i < active_list.size(); ++i) {
+ auto& previous = active_list[i - 1];
+ auto& current = active_list[i];
+
+ IntPoint from, to;
+ IntPoint truncated_from { previous.x, scanline };
+ IntPoint truncated_to { current.x, scanline };
+ approximately_place_on_int_grid({ previous.x, scanline }, { current.x, scanline }, from, to, previous_to);
+
+ if (is_inside_shape(winding_number)) {
+ // The points between this segment and the previous are
+ // inside the shape
+#ifdef FILL_PATH_DEBUG
+ dbg() << "y=" << scanline << ": " << winding_number << " at " << i << ": " << from << " -- " << to;
+#endif
+ draw_line(from, to, color, 1);
+ }
+
+ auto is_passing_through_maxima = scanline == previous.maximum_y
+ || scanline == previous.minimum_y
+ || scanline == current.maximum_y
+ || scanline == current.minimum_y;
+
+ auto is_passing_through_vertex = false;
+
+ if (is_passing_through_maxima) {
+ is_passing_through_vertex = previous.x == current.x;
+ }
+
+ if (!is_passing_through_vertex || previous.inverse_slope * current.inverse_slope < 0)
+ increment_winding(winding_number, truncated_from, truncated_to);
+
+ // update the x coord
+ active_list[i - 1].x -= active_list[i - 1].inverse_slope;
+ }
+ active_list.last().x -= active_list.last().inverse_slope;
+ } else {
+ auto point = IntPoint(active_list[0].x, scanline);
+ draw_line(point, point, color);
+
+ // update the x coord
+ active_list.first().x -= active_list.first().inverse_slope;
+ }
+ }
+
+ --scanline;
+ // remove any edge that goes out of bound from the active list
+ for (size_t i = 0, count = active_list.size(); i < count; ++i) {
+ if (scanline <= active_list[i].minimum_y) {
+ active_list.remove(i);
+ --count;
+ --i;
+ }
+ }
+ for (size_t j = last_active_segment; j < segments.size(); ++j, ++last_active_segment) {
+ auto& segment = segments[j];
+ if (segment.maximum_y < scanline)
+ break;
+ if (segment.minimum_y >= scanline)
+ continue;
+
+ active_list.append(segment);
+ }
+ }
+
+#ifdef FILL_PATH_DEBUG
+ size_t i { 0 };
+ for (auto& segment : segments) {
+ draw_line(Point<int>(segment.from), Point<int>(segment.to), Color::from_hsv(i++ * 360.0 / segments.size(), 1.0, 1.0), 1);
+ }
+#endif
+}
+
+void Painter::blit_disabled(const IntPoint& location, const Gfx::Bitmap& bitmap, const IntRect& rect, const Palette& palette)
+{
+ auto bright_color = palette.threed_highlight();
+ auto dark_color = palette.threed_shadow1();
+ blit_filtered(location.translated(1, 1), bitmap, rect, [&](auto) {
+ return bright_color;
+ });
+ blit_filtered(location, bitmap, rect, [&](Color src) {
+ int gray = src.to_grayscale().red();
+ if (gray > 160)
+ return bright_color;
+ return dark_color;
+ });
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h
new file mode 100644
index 0000000000..8ae33fc207
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Painter.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/Vector.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+#include <LibGfx/Size.h>
+#include <LibGfx/TextAlignment.h>
+#include <LibGfx/TextElision.h>
+
+namespace Gfx {
+
+class Painter {
+public:
+ explicit Painter(Gfx::Bitmap&);
+ ~Painter();
+
+ enum class LineStyle {
+ Solid,
+ Dotted,
+ Dashed,
+ };
+
+ void clear_rect(const IntRect&, Color);
+ void fill_rect(const IntRect&, Color);
+ void fill_rect_with_dither_pattern(const IntRect&, Color, Color);
+ void fill_rect_with_checkerboard(const IntRect&, const IntSize&, Color color_dark, Color color_light);
+ void fill_rect_with_gradient(Orientation, const IntRect&, Color gradient_start, Color gradient_end);
+ void fill_rect_with_gradient(const IntRect&, Color gradient_start, Color gradient_end);
+ void fill_ellipse(const IntRect&, Color);
+ void draw_rect(const IntRect&, Color, bool rough = false);
+ void draw_focus_rect(const IntRect&, Color);
+ void draw_bitmap(const IntPoint&, const CharacterBitmap&, Color = Color());
+ void draw_bitmap(const IntPoint&, const GlyphBitmap&, Color = Color());
+ void draw_scaled_bitmap(const IntRect& dst_rect, const Gfx::Bitmap&, const IntRect& src_rect, float opacity = 1.0f);
+ void draw_triangle(const IntPoint&, const IntPoint&, const IntPoint&, Color);
+ void draw_ellipse_intersecting(const IntRect&, Color, int thickness = 1);
+ void set_pixel(const IntPoint&, Color);
+ void set_pixel(int x, int y, Color color) { set_pixel({ x, y }, color); }
+ void draw_line(const IntPoint&, const IntPoint&, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
+ void draw_quadratic_bezier_curve(const IntPoint& control_point, const IntPoint&, const IntPoint&, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
+ void draw_elliptical_arc(const IntPoint& p1, const IntPoint& p2, const IntPoint& center, const FloatPoint& radii, float x_axis_rotation, float theta_1, float theta_delta, Color, int thickness = 1, LineStyle style = LineStyle::Solid);
+ void blit(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity = 1.0f);
+ void blit_dimmed(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect);
+ void blit_brightened(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect);
+ void blit_filtered(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, Function<Color(Color)>);
+ void draw_tiled_bitmap(const IntRect& dst_rect, const Gfx::Bitmap&);
+ void blit_offset(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, const IntPoint&);
+ void blit_scaled(const IntRect&, const Gfx::Bitmap&, const IntRect&, float, float);
+ void blit_disabled(const IntPoint&, const Gfx::Bitmap&, const IntRect&, const Palette&);
+ void draw_text(const IntRect&, const StringView&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(const IntRect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(const IntRect&, const Utf32View&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
+ void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const StringView&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
+ void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const Utf8View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
+ void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
+ void draw_glyph(const IntPoint&, u32, Color);
+ void draw_glyph(const IntPoint&, u32, const Font&, Color);
+ void draw_emoji(const IntPoint&, const Gfx::Bitmap&, const Font&);
+ void draw_glyph_or_emoji(const IntPoint&, u32 code_point, const Font&, Color);
+
+ static void for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&);
+ static void for_each_line_segment_on_bezier_curve(const FloatPoint& control_point, const FloatPoint& p1, const FloatPoint& p2, Function<void(const FloatPoint&, const FloatPoint&)>&&);
+
+ static void for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>&);
+ static void for_each_line_segment_on_elliptical_arc(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta, Function<void(const FloatPoint&, const FloatPoint&)>&&);
+
+ void stroke_path(const Path&, Color, int thickness);
+
+ enum class WindingRule {
+ Nonzero,
+ EvenOdd,
+ };
+ void fill_path(Path&, Color, WindingRule rule = WindingRule::Nonzero);
+
+ const Font& font() const { return *state().font; }
+ void set_font(const Font& font) { state().font = &font; }
+
+ enum class DrawOp {
+ Copy,
+ Xor,
+ Invert
+ };
+ void set_draw_op(DrawOp op) { state().draw_op = op; }
+ DrawOp draw_op() const { return state().draw_op; }
+
+ void add_clip_rect(const IntRect& rect);
+ void clear_clip_rect();
+ IntRect clip_rect() const { return state().clip_rect; }
+
+ void translate(int dx, int dy) { state().translation.move_by(dx, dy); }
+ void translate(const IntPoint& delta) { state().translation.move_by(delta); }
+
+ IntPoint translation() const { return state().translation; }
+
+ Gfx::Bitmap* target() { return m_target.ptr(); }
+
+ void save() { m_state_stack.append(m_state_stack.last()); }
+ void restore()
+ {
+ ASSERT(m_state_stack.size() > 1);
+ m_state_stack.take_last();
+ }
+
+protected:
+ void set_pixel_with_draw_op(u32& pixel, const Color&);
+ void fill_scanline_with_draw_op(int y, int x, int width, const Color& color);
+ void fill_rect_with_draw_op(const IntRect&, Color);
+ void blit_with_alpha(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect);
+ void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity);
+ void draw_pixel(const IntPoint&, Color, int thickness = 1);
+
+ struct State {
+ const Font* font;
+ IntPoint translation;
+ IntRect clip_rect;
+ DrawOp draw_op;
+ };
+
+ State& state() { return m_state_stack.last(); }
+ const State& state() const { return m_state_stack.last(); }
+
+ IntRect m_clip_origin;
+ NonnullRefPtr<Gfx::Bitmap> m_target;
+ Vector<State, 4> m_state_stack;
+};
+
+class PainterStateSaver {
+public:
+ explicit PainterStateSaver(Painter&);
+ ~PainterStateSaver();
+
+private:
+ Painter& m_painter;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Palette.cpp b/Userland/Libraries/LibGfx/Palette.cpp
new file mode 100644
index 0000000000..de6915d7ba
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Palette.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Badge.h>
+#include <AK/SharedBuffer.h>
+#include <LibGfx/Palette.h>
+#include <string.h>
+
+namespace Gfx {
+
+NonnullRefPtr<PaletteImpl> PaletteImpl::create_with_shared_buffer(SharedBuffer& buffer)
+{
+ return adopt(*new PaletteImpl(buffer));
+}
+
+PaletteImpl::PaletteImpl(SharedBuffer& buffer)
+ : m_theme_buffer(buffer)
+{
+}
+
+Palette::Palette(const PaletteImpl& impl)
+ : m_impl(impl)
+{
+}
+
+Palette::~Palette()
+{
+}
+
+const SystemTheme& PaletteImpl::theme() const
+{
+ return *m_theme_buffer->data<SystemTheme>();
+}
+
+Color PaletteImpl::color(ColorRole role) const
+{
+ ASSERT((int)role < (int)ColorRole::__Count);
+ return Color::from_rgba(theme().color[(int)role]);
+}
+
+int PaletteImpl::metric(MetricRole role) const
+{
+ ASSERT((int)role < (int)MetricRole::__Count);
+ return theme().metric[(int)role];
+}
+
+String PaletteImpl::path(PathRole role) const
+{
+ ASSERT((int)role < (int)PathRole::__Count);
+ return theme().path[(int)role];
+}
+
+NonnullRefPtr<PaletteImpl> PaletteImpl::clone() const
+{
+ auto new_theme_buffer = SharedBuffer::create_with_size(m_theme_buffer->size());
+ memcpy(new_theme_buffer->data<SystemTheme>(), &theme(), m_theme_buffer->size());
+ return adopt(*new PaletteImpl(*new_theme_buffer));
+}
+
+void Palette::set_color(ColorRole role, Color color)
+{
+ if (m_impl->ref_count() != 1)
+ m_impl = m_impl->clone();
+ auto& theme = const_cast<SystemTheme&>(impl().theme());
+ theme.color[(int)role] = color.value();
+}
+
+void Palette::set_metric(MetricRole role, int value)
+{
+ if (m_impl->ref_count() != 1)
+ m_impl = m_impl->clone();
+ auto& theme = const_cast<SystemTheme&>(impl().theme());
+ theme.metric[(int)role] = value;
+}
+
+void Palette::set_path(PathRole role, String path)
+{
+ if (m_impl->ref_count() != 1)
+ m_impl = m_impl->clone();
+ auto& theme = const_cast<SystemTheme&>(impl().theme());
+ memcpy(theme.path[(int)role], path.characters(), min(path.length() + 1, sizeof(theme.path[(int)role])));
+ theme.path[(int)role][sizeof(theme.path[(int)role]) - 1] = '\0';
+}
+
+PaletteImpl::~PaletteImpl()
+{
+}
+
+void PaletteImpl::replace_internal_buffer(Badge<GUI::Application>, SharedBuffer& buffer)
+{
+ m_theme_buffer = buffer;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Palette.h b/Userland/Libraries/LibGfx/Palette.h
new file mode 100644
index 0000000000..dfd47e90ff
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Palette.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/Noncopyable.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/SystemTheme.h>
+
+namespace Gfx {
+
+class PaletteImpl : public RefCounted<PaletteImpl> {
+ AK_MAKE_NONCOPYABLE(PaletteImpl);
+ AK_MAKE_NONMOVABLE(PaletteImpl);
+
+public:
+ ~PaletteImpl();
+ static NonnullRefPtr<PaletteImpl> create_with_shared_buffer(SharedBuffer&);
+ NonnullRefPtr<PaletteImpl> clone() const;
+
+ Color color(ColorRole) const;
+ int metric(MetricRole) const;
+ String path(PathRole) const;
+ const SystemTheme& theme() const;
+
+ void replace_internal_buffer(Badge<GUI::Application>, SharedBuffer& buffer);
+
+private:
+ explicit PaletteImpl(SharedBuffer&);
+
+ RefPtr<SharedBuffer> m_theme_buffer;
+};
+
+class Palette {
+
+public:
+ explicit Palette(const PaletteImpl&);
+ ~Palette();
+
+ Color window() const { return color(ColorRole::Window); }
+ Color window_text() const { return color(ColorRole::WindowText); }
+ Color selection() const { return color(ColorRole::Selection); }
+ Color selection_text() const { return color(ColorRole::SelectionText); }
+ Color inactive_selection() const { return color(ColorRole::InactiveSelection); }
+ Color inactive_selection_text() const { return color(ColorRole::InactiveSelectionText); }
+ Color desktop_background() const { return color(ColorRole::DesktopBackground); }
+ Color active_window_border1() const { return color(ColorRole::ActiveWindowBorder1); }
+ Color active_window_border2() const { return color(ColorRole::ActiveWindowBorder2); }
+ Color active_window_title() const { return color(ColorRole::ActiveWindowTitle); }
+ Color active_window_title_stripes() const { return color(ColorRole::ActiveWindowTitleStripes); }
+ Color active_window_title_shadow() const { return color(ColorRole::ActiveWindowTitleShadow); }
+ Color inactive_window_border1() const { return color(ColorRole::InactiveWindowBorder1); }
+ Color inactive_window_border2() const { return color(ColorRole::InactiveWindowBorder2); }
+ Color inactive_window_title() const { return color(ColorRole::InactiveWindowTitle); }
+ Color inactive_window_title_stripes() const { return color(ColorRole::InactiveWindowTitleStripes); }
+ Color inactive_window_title_shadow() const { return color(ColorRole::InactiveWindowTitleShadow); }
+ Color moving_window_border1() const { return color(ColorRole::MovingWindowBorder1); }
+ Color moving_window_border2() const { return color(ColorRole::MovingWindowBorder2); }
+ Color moving_window_title() const { return color(ColorRole::MovingWindowTitle); }
+ Color moving_window_title_stripes() const { return color(ColorRole::MovingWindowTitleStripes); }
+ Color moving_window_title_shadow() const { return color(ColorRole::MovingWindowTitleShadow); }
+ Color highlight_window_border1() const { return color(ColorRole::HighlightWindowBorder1); }
+ Color highlight_window_border2() const { return color(ColorRole::HighlightWindowBorder2); }
+ Color highlight_window_title() const { return color(ColorRole::HighlightWindowTitle); }
+ Color highlight_window_title_stripes() const { return color(ColorRole::HighlightWindowTitleStripes); }
+ Color highlight_window_title_shadow() const { return color(ColorRole::HighlightWindowTitleShadow); }
+ Color highlight_searching() const { return color(ColorRole::HighlightSearching); }
+ Color highlight_searching_text() const { return color(ColorRole::HighlightSearchingText); }
+ Color menu_stripe() const { return color(ColorRole::MenuStripe); }
+ Color menu_base() const { return color(ColorRole::MenuBase); }
+ Color menu_base_text() const { return color(ColorRole::MenuBaseText); }
+ Color menu_selection() const { return color(ColorRole::MenuSelection); }
+ Color menu_selection_text() const { return color(ColorRole::MenuSelectionText); }
+ Color base() const { return color(ColorRole::Base); }
+ Color base_text() const { return color(ColorRole::BaseText); }
+ Color button() const { return color(ColorRole::Button); }
+ Color button_text() const { return color(ColorRole::ButtonText); }
+ Color threed_highlight() const { return color(ColorRole::ThreedHighlight); }
+ Color threed_shadow1() const { return color(ColorRole::ThreedShadow1); }
+ Color threed_shadow2() const { return color(ColorRole::ThreedShadow2); }
+ Color hover_highlight() const { return color(ColorRole::HoverHighlight); }
+ Color rubber_band_fill() const { return color(ColorRole::RubberBandFill); }
+ Color rubber_band_border() const { return color(ColorRole::RubberBandBorder); }
+ Color ruler() const { return color(ColorRole::Ruler); }
+ Color ruler_border() const { return color(ColorRole::RulerBorder); }
+ Color ruler_active_text() const { return color(ColorRole::RulerActiveText); }
+ Color ruler_inactive_text() const { return color(ColorRole::RulerInactiveText); }
+ Color text_cursor() const { return color(ColorRole::TextCursor); }
+ Color focus_outline() const { return color(ColorRole::FocusOutline); }
+
+ Color link() const { return color(ColorRole::Link); }
+ Color active_link() const { return color(ColorRole::ActiveLink); }
+ Color visited_link() const { return color(ColorRole::VisitedLink); }
+
+ Color syntax_comment() const { return color(ColorRole::SyntaxComment); }
+ Color syntax_number() const { return color(ColorRole::SyntaxNumber); }
+ Color syntax_string() const { return color(ColorRole::SyntaxString); }
+ Color syntax_identifier() const { return color(ColorRole::SyntaxIdentifier); }
+ Color syntax_type() const { return color(ColorRole::SyntaxType); }
+ Color syntax_punctuation() const { return color(ColorRole::SyntaxPunctuation); }
+ Color syntax_operator() const { return color(ColorRole::SyntaxOperator); }
+ Color syntax_keyword() const { return color(ColorRole::SyntaxKeyword); }
+ Color syntax_control_keyword() const { return color(ColorRole::SyntaxControlKeyword); }
+ Color syntax_preprocessor_statement() const { return color(ColorRole::SyntaxPreprocessorStatement); }
+ Color syntax_preprocessor_value() const { return color(ColorRole::SyntaxPreprocessorValue); }
+
+ int window_title_height() const { return metric(MetricRole::TitleHeight); }
+ int window_title_button_width() const { return metric(MetricRole::TitleButtonWidth); }
+ int window_title_button_height() const { return metric(MetricRole::TitleButtonHeight); }
+
+ String title_button_icons_path() const { return path(PathRole::TitleButtonIcons); }
+
+ Color color(ColorRole role) const { return m_impl->color(role); }
+ int metric(MetricRole role) const { return m_impl->metric(role); }
+ String path(PathRole role) const { return m_impl->path(role); }
+
+ void set_color(ColorRole, Color);
+ void set_metric(MetricRole, int);
+ void set_path(PathRole, String);
+
+ const SystemTheme& theme() const { return m_impl->theme(); }
+
+ PaletteImpl& impl() { return *m_impl; }
+ const PaletteImpl& impl() const { return *m_impl; }
+
+private:
+ NonnullRefPtr<PaletteImpl> m_impl;
+};
+
+}
+
+using Gfx::Palette;
diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp
new file mode 100644
index 0000000000..5fe2bb2f63
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Path.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/HashTable.h>
+#include <AK/QuickSort.h>
+#include <AK/StringBuilder.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Path.h>
+
+namespace Gfx {
+
+void Path::close()
+{
+ if (m_segments.size() <= 1)
+ return;
+
+ invalidate_split_lines();
+
+ auto& last_point = m_segments.last().point();
+
+ for (ssize_t i = m_segments.size() - 1; i >= 0; --i) {
+ auto& segment = m_segments[i];
+ if (segment.type() == Segment::Type::MoveTo) {
+ if (last_point == segment.point())
+ return;
+ append_segment<LineSegment>(segment.point());
+ return;
+ }
+ }
+}
+
+void Path::close_all_subpaths()
+{
+ if (m_segments.size() <= 1)
+ return;
+
+ invalidate_split_lines();
+
+ Optional<FloatPoint> cursor, start_of_subpath;
+ bool is_first_point_in_subpath { false };
+
+ for (auto& segment : m_segments) {
+ switch (segment.type()) {
+ case Segment::Type::MoveTo: {
+ if (cursor.has_value() && !is_first_point_in_subpath) {
+ // This is a move from a subpath to another
+ // connect the two ends of this subpath before
+ // moving on to the next one
+ ASSERT(start_of_subpath.has_value());
+
+ append_segment<MoveSegment>(cursor.value());
+ append_segment<LineSegment>(start_of_subpath.value());
+ }
+ is_first_point_in_subpath = true;
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::LineTo:
+ case Segment::Type::QuadraticBezierCurveTo:
+ case Segment::Type::EllipticalArcTo:
+ if (is_first_point_in_subpath) {
+ start_of_subpath = cursor;
+ is_first_point_in_subpath = false;
+ }
+ cursor = segment.point();
+ break;
+ case Segment::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+}
+
+String Path::to_string() const
+{
+ StringBuilder builder;
+ builder.append("Path { ");
+ for (auto& segment : m_segments) {
+ switch (segment.type()) {
+ case Segment::Type::MoveTo:
+ builder.append("MoveTo");
+ break;
+ case Segment::Type::LineTo:
+ builder.append("LineTo");
+ break;
+ case Segment::Type::QuadraticBezierCurveTo:
+ builder.append("QuadraticBezierCurveTo");
+ break;
+ case Segment::Type::EllipticalArcTo:
+ builder.append("EllipticalArcTo");
+ break;
+ case Segment::Type::Invalid:
+ builder.append("Invalid");
+ break;
+ }
+ builder.appendf("(%s", segment.point().to_string().characters());
+
+ switch (segment.type()) {
+ case Segment::Type::QuadraticBezierCurveTo:
+ builder.append(", ");
+ builder.append(static_cast<const QuadraticBezierCurveSegment&>(segment).through().to_string());
+ break;
+ case Segment::Type::EllipticalArcTo: {
+ auto& arc = static_cast<const EllipticalArcSegment&>(segment);
+ builder.appendf(", %s, %s, %f, %f, %f",
+ arc.radii().to_string().characters(),
+ arc.center().to_string().characters(),
+ arc.x_axis_rotation(),
+ arc.theta_1(),
+ arc.theta_delta());
+ break;
+ }
+ default:
+ break;
+ }
+
+ builder.append(") ");
+ }
+ builder.append("}");
+ return builder.to_string();
+}
+
+void Path::segmentize_path()
+{
+ Vector<SplitLineSegment> segments;
+ float min_x = 0;
+ float min_y = 0;
+ float max_x = 0;
+ float max_y = 0;
+
+ auto add_point_to_bbox = [&](const Gfx::FloatPoint& point) {
+ float x = point.x();
+ float y = point.y();
+ min_x = min(min_x, x);
+ min_y = min(min_y, y);
+ max_x = max(max_x, x);
+ max_y = max(max_y, y);
+ };
+
+ auto add_line = [&](const auto& p0, const auto& p1) {
+ float ymax = p0.y(), ymin = p1.y(), x_of_ymin = p1.x(), x_of_ymax = p0.x();
+ auto slope = p0.x() == p1.x() ? 0 : ((float)(p0.y() - p1.y())) / ((float)(p0.x() - p1.x()));
+ if (p0.y() < p1.y()) {
+ swap(ymin, ymax);
+ swap(x_of_ymin, x_of_ymax);
+ }
+
+ segments.append({ FloatPoint(p0.x(), p0.y()),
+ FloatPoint(p1.x(), p1.y()),
+ slope == 0 ? 0 : 1 / slope,
+ x_of_ymin,
+ ymax, ymin, x_of_ymax });
+
+ add_point_to_bbox(p1);
+ };
+
+ FloatPoint cursor { 0, 0 };
+ bool first = true;
+
+ for (auto& segment : m_segments) {
+ switch (segment.type()) {
+ case Segment::Type::MoveTo:
+ if (first) {
+ min_x = segment.point().x();
+ min_y = segment.point().y();
+ max_x = segment.point().x();
+ max_y = segment.point().y();
+ } else {
+ add_point_to_bbox(segment.point());
+ }
+ cursor = segment.point();
+ break;
+ case Segment::Type::LineTo: {
+ add_line(cursor, segment.point());
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::QuadraticBezierCurveTo: {
+ auto& control = static_cast<QuadraticBezierCurveSegment&>(segment).through();
+ Painter::for_each_line_segment_on_bezier_curve(control, cursor, segment.point(), [&](const FloatPoint& p0, const FloatPoint& p1) {
+ add_line(p0, p1);
+ });
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::EllipticalArcTo: {
+ auto& arc = static_cast<EllipticalArcSegment&>(segment);
+ Painter::for_each_line_segment_on_elliptical_arc(cursor, arc.point(), arc.center(), arc.radii(), arc.x_axis_rotation(), arc.theta_1(), arc.theta_delta(), [&](const FloatPoint& p0, const FloatPoint& p1) {
+ add_line(p0, p1);
+ });
+ cursor = segment.point();
+ break;
+ }
+ case Segment::Type::Invalid:
+ ASSERT_NOT_REACHED();
+ }
+
+ first = false;
+ }
+
+ // sort segments by ymax
+ quick_sort(segments, [](const auto& line0, const auto& line1) {
+ return line1.maximum_y < line0.maximum_y;
+ });
+
+ m_split_lines = move(segments);
+ m_bounding_box = Gfx::FloatRect { min_x, min_y, max_x - min_x, max_y - min_y };
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Path.h b/Userland/Libraries/LibGfx/Path.h
new file mode 100644
index 0000000000..7a8dfe9567
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Path.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/Optional.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+
+namespace Gfx {
+
+class Segment : public RefCounted<Segment> {
+public:
+ enum class Type {
+ Invalid,
+ MoveTo,
+ LineTo,
+ QuadraticBezierCurveTo,
+ EllipticalArcTo,
+ };
+
+ Segment(const FloatPoint& point)
+ : m_point(point)
+ {
+ }
+
+ virtual ~Segment() = default;
+
+ const FloatPoint& point() const { return m_point; }
+ virtual Type type() const = 0;
+
+protected:
+ FloatPoint m_point;
+};
+
+class MoveSegment final : public Segment {
+public:
+ MoveSegment(const FloatPoint& point)
+ : Segment(point)
+ {
+ }
+
+private:
+ virtual Type type() const override { return Segment::Type::MoveTo; }
+};
+
+class LineSegment final : public Segment {
+public:
+ LineSegment(const FloatPoint& point)
+ : Segment(point)
+ {
+ }
+
+ virtual ~LineSegment() override = default;
+
+private:
+ virtual Type type() const override { return Segment::Type::LineTo; }
+};
+
+class QuadraticBezierCurveSegment final : public Segment {
+public:
+ QuadraticBezierCurveSegment(const FloatPoint& point, const FloatPoint& through)
+ : Segment(point)
+ , m_through(through)
+ {
+ }
+
+ virtual ~QuadraticBezierCurveSegment() override = default;
+
+ const FloatPoint& through() const { return m_through; }
+
+private:
+ virtual Type type() const override { return Segment::Type::QuadraticBezierCurveTo; }
+
+ FloatPoint m_through;
+};
+
+class EllipticalArcSegment final : public Segment {
+public:
+ EllipticalArcSegment(const FloatPoint& point, const FloatPoint& center, const FloatPoint radii, float x_axis_rotation, float theta_1, float theta_delta)
+ : Segment(point)
+ , m_center(center)
+ , m_radii(radii)
+ , m_x_axis_rotation(x_axis_rotation)
+ , m_theta_1(theta_1)
+ , m_theta_delta(theta_delta)
+ {
+ }
+
+ virtual ~EllipticalArcSegment() override = default;
+
+ const FloatPoint& center() const { return m_center; }
+ const FloatPoint& radii() const { return m_radii; }
+ float x_axis_rotation() const { return m_x_axis_rotation; }
+ float theta_1() const { return m_theta_1; }
+ float theta_delta() const { return m_theta_delta; }
+
+private:
+ virtual Type type() const override { return Segment::Type::EllipticalArcTo; }
+
+ FloatPoint m_center;
+ FloatPoint m_radii;
+ float m_x_axis_rotation;
+ float m_theta_1;
+ float m_theta_delta;
+};
+
+class Path {
+public:
+ Path() { }
+
+ void move_to(const FloatPoint& point)
+ {
+ append_segment<MoveSegment>(point);
+ }
+
+ void line_to(const FloatPoint& point)
+ {
+ append_segment<LineSegment>(point);
+ invalidate_split_lines();
+ }
+
+ void quadratic_bezier_curve_to(const FloatPoint& through, const FloatPoint& point)
+ {
+ append_segment<QuadraticBezierCurveSegment>(point, through);
+ invalidate_split_lines();
+ }
+
+ void elliptical_arc_to(const FloatPoint& point, const FloatPoint& center, const FloatPoint& radii, float x_axis_rotation, float theta_1, float theta_delta)
+ {
+ append_segment<EllipticalArcSegment>(point, center, radii, x_axis_rotation, theta_1, theta_delta);
+ invalidate_split_lines();
+ }
+
+ void close();
+ void close_all_subpaths();
+
+ struct SplitLineSegment {
+ FloatPoint from, to;
+ float inverse_slope;
+ float x_of_minimum_y;
+ float maximum_y;
+ float minimum_y;
+ float x;
+ };
+
+ const NonnullRefPtrVector<Segment>& segments() const { return m_segments; }
+ const auto& split_lines()
+ {
+ if (!m_split_lines.has_value()) {
+ segmentize_path();
+ ASSERT(m_split_lines.has_value());
+ }
+ return m_split_lines.value();
+ }
+
+ const Gfx::FloatRect& bounding_box()
+ {
+ if (!m_bounding_box.has_value()) {
+ segmentize_path();
+ ASSERT(m_bounding_box.has_value());
+ }
+ return m_bounding_box.value();
+ }
+
+ String to_string() const;
+
+private:
+ void invalidate_split_lines()
+ {
+ m_split_lines.clear();
+ }
+ void segmentize_path();
+
+ template<typename T, typename... Args>
+ void append_segment(Args&&... args)
+ {
+ m_segments.append(adopt(*new T(forward<Args>(args)...)));
+ }
+
+ NonnullRefPtrVector<Segment> m_segments {};
+
+ Optional<Vector<SplitLineSegment>> m_split_lines {};
+ Optional<Gfx::FloatRect> m_bounding_box;
+};
+
+inline const LogStream& operator<<(const LogStream& stream, const Path& path)
+{
+ return stream << path.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Point.cpp b/Userland/Libraries/LibGfx/Point.cpp
new file mode 100644
index 0000000000..27ae1248ba
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Point.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Rect.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+template<typename T>
+void Point<T>::constrain(const Rect<T>& rect)
+{
+ if (x() < rect.left()) {
+ set_x(rect.left());
+ } else if (x() > rect.right()) {
+ set_x(rect.right());
+ }
+
+ if (y() < rect.top()) {
+ set_y(rect.top());
+ } else if (y() > rect.bottom()) {
+ set_y(rect.bottom());
+ }
+}
+
+template<>
+String IntPoint::to_string() const
+{
+ return String::formatted("[{},{}]", x(), y());
+}
+
+template<>
+String FloatPoint::to_string() const
+{
+ return String::formatted("[{},{}]", x(), y());
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::IntPoint& point)
+{
+ encoder << point.x() << point.y();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::IntPoint& point)
+{
+ int x = 0;
+ int y = 0;
+ if (!decoder.decode(x))
+ return false;
+ if (!decoder.decode(y))
+ return false;
+ point = { x, y };
+ return true;
+}
+
+}
+
+template class Gfx::Point<int>;
+template class Gfx::Point<float>;
diff --git a/Userland/Libraries/LibGfx/Point.h b/Userland/Libraries/LibGfx/Point.h
new file mode 100644
index 0000000000..753d04055a
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Point.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <AK/StdLibExtras.h>
+#include <LibGfx/Forward.h>
+#include <LibGfx/Orientation.h>
+#include <LibIPC/Forward.h>
+#include <math.h>
+#include <stdlib.h>
+
+namespace Gfx {
+
+template<typename T>
+class Point {
+public:
+ Point() { }
+
+ Point(T x, T y)
+ : m_x(x)
+ , m_y(y)
+ {
+ }
+
+ template<typename U>
+ Point(U x, U y)
+ : m_x(x)
+ , m_y(y)
+ {
+ }
+
+ template<typename U>
+ explicit Point(const Point<U>& other)
+ : m_x(other.x())
+ , m_y(other.y())
+ {
+ }
+
+ T x() const { return m_x; }
+ T y() const { return m_y; }
+
+ void set_x(T x) { m_x = x; }
+ void set_y(T y) { m_y = y; }
+
+ void move_by(T dx, T dy)
+ {
+ m_x += dx;
+ m_y += dy;
+ }
+
+ void move_by(const Point<T>& delta)
+ {
+ move_by(delta.x(), delta.y());
+ }
+
+ Point<T> translated(const Point<T>& delta) const
+ {
+ Point<T> point = *this;
+ point.move_by(delta);
+ return point;
+ }
+
+ Point<T> translated(T dx, T dy) const
+ {
+ Point<T> point = *this;
+ point.move_by(dx, dy);
+ return point;
+ }
+
+ Point<T> translated(T dboth) const
+ {
+ Point<T> point = *this;
+ point.move_by(dboth, dboth);
+ return point;
+ }
+
+ void constrain(const Rect<T>&);
+ Point<T> constrained(const Rect<T>& rect) const
+ {
+ Point<T> point = *this;
+ point.constrain(rect);
+ return point;
+ }
+
+ bool operator==(const Point<T>& other) const
+ {
+ return m_x == other.m_x && m_y == other.m_y;
+ }
+
+ bool operator!=(const Point<T>& other) const
+ {
+ return !(*this == other);
+ }
+
+ Point<T> operator+(const Point<T>& other) const { return { m_x + other.m_x, m_y + other.m_y }; }
+
+ Point<T>& operator+=(const Point<T>& other)
+ {
+ m_x += other.m_x;
+ m_y += other.m_y;
+ return *this;
+ }
+
+ Point<T> operator-() const { return { -m_x, -m_y }; }
+
+ Point<T> operator-(const Point<T>& other) const { return { m_x - other.m_x, m_y - other.m_y }; }
+
+ Point<T>& operator-=(const Point<T>& other)
+ {
+ m_x -= other.m_x;
+ m_y -= other.m_y;
+ return *this;
+ }
+
+ Point<T> operator*(T factor) const { return { m_x * factor, m_y * factor }; }
+
+ Point<T>& operator*=(T factor)
+ {
+ m_x *= factor;
+ m_y *= factor;
+ return *this;
+ }
+
+ Point<T> operator/(T factor) const { return { m_x / factor, m_y / factor }; }
+
+ Point<T>& operator/=(T factor)
+ {
+ m_x /= factor;
+ m_y /= factor;
+ return *this;
+ }
+
+ bool is_null() const { return !m_x && !m_y; }
+
+ T primary_offset_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? y() : x();
+ }
+
+ void set_primary_offset_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_y(value);
+ } else {
+ set_x(value);
+ }
+ }
+
+ T secondary_offset_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? x() : y();
+ }
+
+ void set_secondary_offset_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_x(value);
+ } else {
+ set_y(value);
+ }
+ }
+
+ T dx_relative_to(const Point<T>& other) const
+ {
+ return x() - other.x();
+ }
+
+ T dy_relative_to(const Point<T>& other) const
+ {
+ return y() - other.y();
+ }
+
+ // Returns pixels moved from other in either direction
+ T pixels_moved(const Point<T>& other) const
+ {
+ return max(abs(dx_relative_to(other)), abs(dy_relative_to(other)));
+ }
+
+ float distance_from(const Point<T>& other) const
+ {
+ if (*this == other)
+ return 0;
+ return sqrtf(powf(m_x - other.m_x, 2.0f) + powf(m_y - other.m_y, 2.0f));
+ }
+
+ Point absolute_relative_distance_to(const Point& other) const
+ {
+ return { abs(dx_relative_to(other)), abs(dy_relative_to(other)) };
+ }
+
+ template<typename U>
+ Point<U> to_type() const
+ {
+ return Point<U>(*this);
+ }
+
+ String to_string() const;
+
+private:
+ T m_x { 0 };
+ T m_y { 0 };
+};
+
+template<typename T>
+const LogStream& operator<<(const LogStream& stream, const Point<T>& point)
+{
+ return stream << point.to_string();
+}
+
+using IntPoint = Point<int>;
+using FloatPoint = Point<float>;
+
+}
+
+namespace AK {
+
+template<typename T>
+struct Formatter<Gfx::Point<T>> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Point<T>& value)
+ {
+ Formatter<StringView>::format(builder, value.to_string());
+ }
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::IntPoint&);
+bool decode(Decoder&, Gfx::IntPoint&);
+
+}
diff --git a/Userland/Libraries/LibGfx/PortableImageLoaderCommon.h b/Userland/Libraries/LibGfx/PortableImageLoaderCommon.h
new file mode 100644
index 0000000000..5f3c51679c
--- /dev/null
+++ b/Userland/Libraries/LibGfx/PortableImageLoaderCommon.h
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Array.h>
+#include <AK/Endian.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/ScopeGuard.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/ImageDecoder.h>
+#include <LibGfx/Streamer.h>
+
+//#define PORTABLE_IMAGE_LOADER_DEBUG
+
+namespace Gfx {
+
+static constexpr Color adjust_color(u16 max_val, Color color)
+{
+ color.set_red((color.red() * 255) / max_val);
+ color.set_green((color.green() * 255) / max_val);
+ color.set_blue((color.blue() * 255) / max_val);
+
+ return color;
+}
+
+template<typename TValue>
+static bool read_number(Streamer& streamer, TValue* value)
+{
+ u8 byte {};
+ StringBuilder sb {};
+
+ while (streamer.read(byte)) {
+ if (byte == ' ' || byte == '\t' || byte == '\n' || byte == '\r') {
+ streamer.step_back();
+ break;
+ }
+
+ sb.append(byte);
+ }
+
+ const auto opt_value = sb.to_string().to_uint();
+ if (!opt_value.has_value()) {
+ *value = 0;
+ return false;
+ }
+
+ *value = static_cast<u16>(opt_value.value());
+ return true;
+}
+
+template<typename TContext>
+static bool read_comment([[maybe_unused]] TContext& context, Streamer& streamer)
+{
+ bool exist = false;
+ u8 byte {};
+
+ while (streamer.read(byte)) {
+ if (byte == '#') {
+ exist = true;
+ } else if (byte == '\t' || byte == '\n') {
+ return exist;
+ }
+ }
+
+ return exist;
+}
+
+template<typename TContext>
+static bool read_magic_number(TContext& context, Streamer& streamer)
+{
+ if (context.state >= TContext::State::MagicNumber) {
+ return true;
+ }
+
+ if (!context.data || context.data_size < 2) {
+ context.state = TContext::State::Error;
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "There is no enough data for " << TContext::image_type;
+#endif
+ return false;
+ }
+
+ u8 magic_number[2] {};
+ if (!streamer.read_bytes(magic_number, 2)) {
+ context.state = TContext::State::Error;
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "We can't read magic number for " << TContext::image_type;
+#endif
+ return false;
+ }
+
+ if (magic_number[0] == 'P' && magic_number[1] == TContext::ascii_magic_number) {
+ context.type = TContext::Type::ASCII;
+ context.state = TContext::State::MagicNumber;
+ return true;
+ }
+
+ if (magic_number[0] == 'P' && magic_number[1] == TContext::binary_magic_number) {
+ context.type = TContext::Type::RAWBITS;
+ context.state = TContext::State::MagicNumber;
+ return true;
+ }
+
+ context.state = TContext::State::Error;
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "Magic number is not valid for "
+ << magic_number[0] << magic_number[1] << TContext::image_type;
+#endif
+ return false;
+}
+
+template<typename TContext>
+static bool read_white_space(TContext& context, Streamer& streamer)
+{
+ bool exist = false;
+ u8 byte {};
+
+ while (streamer.read(byte)) {
+ if (byte == ' ' || byte == '\t' || byte == '\n' || byte == '\r') {
+ exist = true;
+ } else if (byte == '#') {
+ streamer.step_back();
+ read_comment(context, streamer);
+ } else {
+ streamer.step_back();
+ return exist;
+ }
+ }
+
+ return exist;
+}
+
+template<typename TContext>
+static bool read_width(TContext& context, Streamer& streamer)
+{
+ if (const bool result = read_number(streamer, &context.width);
+ !result || context.width == 0) {
+ return false;
+ }
+
+ context.state = TContext::Width;
+ return true;
+}
+
+template<typename TContext>
+static bool read_height(TContext& context, Streamer& streamer)
+{
+ if (const bool result = read_number(streamer, &context.height);
+ !result || context.height == 0) {
+ return false;
+ }
+
+ context.state = TContext::Height;
+ return true;
+}
+
+template<typename TContext>
+static bool read_max_val(TContext& context, Streamer& streamer)
+{
+ if (const bool result = read_number(streamer, &context.max_val);
+ !result || context.max_val == 0) {
+ return false;
+ }
+
+ if (context.max_val > 255) {
+#ifdef PORTABLE_IMAGE_LOADER_DEBUG
+ dbg() << "We can't parse 2 byte color for " << TContext::image_type;
+#endif
+ context.state = TContext::Error;
+ return false;
+ }
+
+ context.state = TContext::Maxval;
+ return true;
+}
+
+template<typename TContext>
+static bool create_bitmap(TContext& context)
+{
+ context.bitmap = Bitmap::create_purgeable(BitmapFormat::RGB32, { context.width, context.height });
+ if (!context.bitmap) {
+ context.state = TContext::State::Error;
+ return false;
+ }
+ return true;
+}
+
+template<typename TContext>
+static void set_pixels(TContext& context, const AK::Vector<Gfx::Color>& color_data)
+{
+ size_t index = 0;
+ for (size_t y = 0; y < context.height; ++y) {
+ for (size_t x = 0; x < context.width; ++x) {
+ context.bitmap->set_pixel(x, y, color_data.at(index));
+ index++;
+ }
+ }
+}
+
+template<typename TContext>
+static bool decode(TContext& context)
+{
+ if (context.state >= TContext::State::Decoded)
+ return true;
+
+ auto error_guard = ArmedScopeGuard([&] {
+ context.state = TContext::State::Error;
+ });
+
+ Streamer streamer(context.data, context.data_size);
+
+ if (!read_magic_number(context, streamer))
+ return false;
+
+ if (!read_white_space(context, streamer))
+ return false;
+
+ if (!read_width(context, streamer))
+ return false;
+
+ if (!read_white_space(context, streamer))
+ return false;
+
+ if (!read_height(context, streamer))
+ return false;
+
+ if (context.width > maximum_width_for_decoded_images || context.height > maximum_height_for_decoded_images) {
+ dbgln("This portable network image is too large for comfort: {}x{}", context.width, context.height);
+ return false;
+ }
+
+ if (!read_white_space(context, streamer))
+ return false;
+
+ if constexpr (requires { context.max_val; }) {
+ if (!read_max_val(context, streamer))
+ return false;
+
+ if (!read_white_space(context, streamer))
+ return false;
+ }
+
+ if (!read_image_data(context, streamer))
+ return false;
+
+ error_guard.disarm();
+ context.state = TContext::State::Decoded;
+ return true;
+}
+
+template<typename TContext>
+static RefPtr<Gfx::Bitmap> load_impl(const u8* data, size_t data_size)
+{
+ TContext context {};
+ context.data = data;
+ context.data_size = data_size;
+
+ if (!decode(context)) {
+ return nullptr;
+ }
+ return context.bitmap;
+}
+template<typename TContext>
+static RefPtr<Gfx::Bitmap> load(const StringView& path)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return nullptr;
+ auto bitmap = load_impl<TContext>((const u8*)file_or_error.value()->data(), file_or_error.value()->size());
+ if (bitmap)
+ bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded {}: {}",
+ bitmap->size(),
+ TContext::image_type,
+ LexicalPath::canonicalized_path(path)));
+ return bitmap;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Rect.cpp b/Userland/Libraries/LibGfx/Rect.cpp
new file mode 100644
index 0000000000..1f0d39d440
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Rect.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/StdLibExtras.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibGfx/Rect.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+template<typename T>
+void Rect<T>::intersect(const Rect<T>& other)
+{
+ T l = max(left(), other.left());
+ T r = min(right(), other.right());
+ T t = max(top(), other.top());
+ T b = min(bottom(), other.bottom());
+
+ if (l > r || t > b) {
+ m_location = {};
+ m_size = {};
+ return;
+ }
+
+ m_location.set_x(l);
+ m_location.set_y(t);
+ m_size.set_width((r - l) + 1);
+ m_size.set_height((b - t) + 1);
+}
+
+template<typename T>
+Rect<T> Rect<T>::united(const Rect<T>& other) const
+{
+ if (is_null())
+ return other;
+ if (other.is_null())
+ return *this;
+ Rect<T> rect;
+ rect.set_left(min(left(), other.left()));
+ rect.set_top(min(top(), other.top()));
+ rect.set_right(max(right(), other.right()));
+ rect.set_bottom(max(bottom(), other.bottom()));
+ return rect;
+}
+
+template<typename T>
+Vector<Rect<T>, 4> Rect<T>::shatter(const Rect<T>& hammer) const
+{
+ Vector<Rect<T>, 4> pieces;
+ if (!intersects(hammer)) {
+ pieces.unchecked_append(*this);
+ return pieces;
+ }
+ Rect<T> top_shard {
+ x(),
+ y(),
+ width(),
+ hammer.y() - y()
+ };
+ Rect<T> bottom_shard {
+ x(),
+ hammer.y() + hammer.height(),
+ width(),
+ (y() + height()) - (hammer.y() + hammer.height())
+ };
+ Rect<T> left_shard {
+ x(),
+ max(hammer.y(), y()),
+ hammer.x() - x(),
+ min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
+ };
+ Rect<T> right_shard {
+ hammer.x() + hammer.width(),
+ max(hammer.y(), y()),
+ right() - hammer.right(),
+ min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
+ };
+ if (!top_shard.is_empty())
+ pieces.unchecked_append(top_shard);
+ if (!bottom_shard.is_empty())
+ pieces.unchecked_append(bottom_shard);
+ if (!left_shard.is_empty())
+ pieces.unchecked_append(left_shard);
+ if (!right_shard.is_empty())
+ pieces.unchecked_append(right_shard);
+
+ return pieces;
+}
+
+template<typename T>
+void Rect<T>::align_within(const Rect<T>& other, TextAlignment alignment)
+{
+ switch (alignment) {
+ case TextAlignment::Center:
+ center_within(other);
+ return;
+ case TextAlignment::TopLeft:
+ set_location(other.location());
+ return;
+ case TextAlignment::TopRight:
+ set_x(other.x() + other.width() - width());
+ set_y(other.y());
+ return;
+ case TextAlignment::CenterLeft:
+ set_x(other.x());
+ center_vertically_within(other);
+ return;
+ case TextAlignment::CenterRight:
+ set_x(other.x() + other.width() - width());
+ center_vertically_within(other);
+ return;
+ case TextAlignment::BottomRight:
+ set_x(other.x() + other.width() - width());
+ set_y(other.y() + other.height() - height());
+ return;
+ }
+}
+
+template<>
+String IntRect::to_string() const
+{
+ return String::format("[%d,%d %dx%d]", x(), y(), width(), height());
+}
+
+template<>
+String FloatRect::to_string() const
+{
+ return String::format("[%f,%f %fx%f]", x(), y(), width(), height());
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::IntRect& rect)
+{
+ encoder << rect.location() << rect.size();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::IntRect& rect)
+{
+ Gfx::IntPoint point;
+ Gfx::IntSize size;
+ if (!decoder.decode(point))
+ return false;
+ if (!decoder.decode(size))
+ return false;
+ rect = { point, size };
+ return true;
+}
+
+}
+
+template class Gfx::Rect<int>;
+template class Gfx::Rect<float>;
diff --git a/Userland/Libraries/LibGfx/Rect.h b/Userland/Libraries/LibGfx/Rect.h
new file mode 100644
index 0000000000..f54ff43fbf
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Rect.h
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <LibGfx/Orientation.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/Size.h>
+#include <LibGfx/TextAlignment.h>
+#include <math.h>
+
+namespace Gfx {
+
+template<typename T>
+T abst(T value)
+{
+ return value < 0 ? -value : value;
+}
+
+template<typename T>
+class Rect {
+public:
+ Rect() { }
+
+ Rect(T x, T y, T width, T height)
+ : m_location(x, y)
+ , m_size(width, height)
+ {
+ }
+
+ template<typename U>
+ Rect(U x, U y, U width, U height)
+ : m_location(x, y)
+ , m_size(width, height)
+ {
+ }
+
+ Rect(const Point<T>& location, const Size<T>& size)
+ : m_location(location)
+ , m_size(size)
+ {
+ }
+
+ template<typename U>
+ Rect(const Point<U>& location, const Size<U>& size)
+ : m_location(location)
+ , m_size(size)
+ {
+ }
+
+ template<typename U>
+ explicit Rect(const Rect<U>& other)
+ : m_location(other.location())
+ , m_size(other.size())
+ {
+ }
+
+ bool is_null() const
+ {
+ return width() == 0 && height() == 0;
+ }
+
+ bool is_empty() const
+ {
+ return width() <= 0 || height() <= 0;
+ }
+
+ void move_by(T dx, T dy)
+ {
+ m_location.move_by(dx, dy);
+ }
+
+ void move_by(const Point<T>& delta)
+ {
+ m_location.move_by(delta);
+ }
+
+ Point<T> center() const
+ {
+ return { x() + width() / 2, y() + height() / 2 };
+ }
+
+ void set_location(const Point<T>& location)
+ {
+ m_location = location;
+ }
+
+ void set_size(const Size<T>& size)
+ {
+ m_size = size;
+ }
+
+ void set_size(T width, T height)
+ {
+ m_size.set_width(width);
+ m_size.set_height(height);
+ }
+
+ void inflate(T w, T h)
+ {
+ set_x(x() - w / 2);
+ set_width(width() + w);
+ set_y(y() - h / 2);
+ set_height(height() + h);
+ }
+
+ void inflate(const Size<T>& size)
+ {
+ set_x(x() - size.width() / 2);
+ set_width(width() + size.width());
+ set_y(y() - size.height() / 2);
+ set_height(height() + size.height());
+ }
+
+ void shrink(T w, T h)
+ {
+ set_x(x() + w / 2);
+ set_width(width() - w);
+ set_y(y() + h / 2);
+ set_height(height() - h);
+ }
+
+ void shrink(const Size<T>& size)
+ {
+ set_x(x() + size.width() / 2);
+ set_width(width() - size.width());
+ set_y(y() + size.height() / 2);
+ set_height(height() - size.height());
+ }
+
+ Rect<T> shrunken(T w, T h) const
+ {
+ Rect<T> rect = *this;
+ rect.shrink(w, h);
+ return rect;
+ }
+
+ Rect<T> shrunken(const Size<T>& size) const
+ {
+ Rect<T> rect = *this;
+ rect.shrink(size);
+ return rect;
+ }
+
+ Rect<T> inflated(T w, T h) const
+ {
+ Rect<T> rect = *this;
+ rect.inflate(w, h);
+ return rect;
+ }
+
+ Rect<T> inflated(const Size<T>& size) const
+ {
+ Rect<T> rect = *this;
+ rect.inflate(size);
+ return rect;
+ }
+
+ Rect<T> translated(T dx, T dy) const
+ {
+ Rect<T> rect = *this;
+ rect.move_by(dx, dy);
+ return rect;
+ }
+
+ Rect<T> translated(const Point<T>& delta) const
+ {
+ Rect<T> rect = *this;
+ rect.move_by(delta);
+ return rect;
+ }
+
+ bool contains_vertically(T y) const
+ {
+ return y >= top() && y <= bottom();
+ }
+
+ bool contains_horizontally(T x) const
+ {
+ return x >= left() && x <= right();
+ }
+
+ bool contains(T x, T y) const
+ {
+ return x >= m_location.x() && x <= right() && y >= m_location.y() && y <= bottom();
+ }
+
+ bool contains(const Point<T>& point) const
+ {
+ return contains(point.x(), point.y());
+ }
+
+ bool contains(const Rect<T>& other) const
+ {
+ return left() <= other.left()
+ && right() >= other.right()
+ && top() <= other.top()
+ && bottom() >= other.bottom();
+ }
+
+ template<typename Container>
+ bool contains(const Container& others) const
+ {
+ bool have_any = false;
+ for (const auto& other : others) {
+ if (!contains(other))
+ return false;
+ have_any = true;
+ }
+ return have_any;
+ }
+
+ int primary_offset_for_orientation(Orientation orientation) const { return m_location.primary_offset_for_orientation(orientation); }
+ void set_primary_offset_for_orientation(Orientation orientation, int value) { m_location.set_primary_offset_for_orientation(orientation, value); }
+ int secondary_offset_for_orientation(Orientation orientation) const { return m_location.secondary_offset_for_orientation(orientation); }
+ void set_secondary_offset_for_orientation(Orientation orientation, int value) { m_location.set_secondary_offset_for_orientation(orientation, value); }
+
+ int primary_size_for_orientation(Orientation orientation) const { return m_size.primary_size_for_orientation(orientation); }
+ int secondary_size_for_orientation(Orientation orientation) const { return m_size.secondary_size_for_orientation(orientation); }
+ void set_primary_size_for_orientation(Orientation orientation, int value) { m_size.set_primary_size_for_orientation(orientation, value); }
+ void set_secondary_size_for_orientation(Orientation orientation, int value) { m_size.set_secondary_size_for_orientation(orientation, value); }
+
+ T first_edge_for_orientation(Orientation orientation) const
+ {
+ if (orientation == Orientation::Vertical)
+ return top();
+ return left();
+ }
+
+ T last_edge_for_orientation(Orientation orientation) const
+ {
+ if (orientation == Orientation::Vertical)
+ return bottom();
+ return right();
+ }
+
+ T left() const { return x(); }
+ T right() const { return x() + width() - 1; }
+ T top() const { return y(); }
+ T bottom() const { return y() + height() - 1; }
+
+ void set_left(T left)
+ {
+ set_x(left);
+ }
+
+ void set_top(T top)
+ {
+ set_y(top);
+ }
+
+ void set_right(T right)
+ {
+ set_width(right - x() + 1);
+ }
+
+ void set_bottom(T bottom)
+ {
+ set_height(bottom - y() + 1);
+ }
+
+ void set_right_without_resize(T new_right)
+ {
+ int delta = new_right - right();
+ move_by(delta, 0);
+ }
+
+ void set_bottom_without_resize(T new_bottom)
+ {
+ int delta = new_bottom - bottom();
+ move_by(0, delta);
+ }
+
+ bool intersects_vertically(const Rect<T>& other) const
+ {
+ return top() <= other.bottom() && other.top() <= bottom();
+ }
+
+ bool intersects_horizontally(const Rect<T>& other) const
+ {
+ return left() <= other.right() && other.left() <= right();
+ }
+
+ bool intersects(const Rect<T>& other) const
+ {
+ return left() <= other.right()
+ && other.left() <= right()
+ && top() <= other.bottom()
+ && other.top() <= bottom();
+ }
+
+ template<typename Container>
+ bool intersects(const Container& others) const
+ {
+ for (const auto& other : others) {
+ if (intersects(other))
+ return true;
+ }
+ return false;
+ }
+
+ template<typename Container, typename Function>
+ IterationDecision for_each_intersected(const Container& others, Function f) const
+ {
+ if (is_empty())
+ return IterationDecision::Continue;
+ for (const auto& other : others) {
+ auto intersected_rect = intersected(other);
+ if (!intersected_rect.is_empty()) {
+ IterationDecision decision = f(intersected_rect);
+ if (decision != IterationDecision::Continue)
+ return decision;
+ }
+ }
+ return IterationDecision::Continue;
+ }
+
+ T x() const { return location().x(); }
+ T y() const { return location().y(); }
+ T width() const { return m_size.width(); }
+ T height() const { return m_size.height(); }
+
+ void set_x(T x) { m_location.set_x(x); }
+ void set_y(T y) { m_location.set_y(y); }
+ void set_width(T width) { m_size.set_width(width); }
+ void set_height(T height) { m_size.set_height(height); }
+
+ const Point<T>& location() const { return m_location; }
+ const Size<T>& size() const { return m_size; }
+
+ Vector<Rect<T>, 4> shatter(const Rect<T>& hammer) const;
+
+ bool operator==(const Rect<T>& other) const
+ {
+ return m_location == other.m_location && m_size == other.m_size;
+ }
+
+ bool operator!=(const Rect<T>& other) const
+ {
+ return !(*this == other);
+ }
+
+ Rect<T> operator*(T factor) const { return { m_location * factor, m_size * factor }; }
+
+ Rect<T>& operator*=(T factor)
+ {
+ m_location *= factor;
+ m_size *= factor;
+ return *this;
+ }
+
+ void intersect(const Rect<T>&);
+
+ static Rect<T> from_two_points(const Point<T>& a, const Point<T>& b)
+ {
+ return { min(a.x(), b.x()), min(a.y(), b.y()), abst(a.x() - b.x()), abst(a.y() - b.y()) };
+ }
+
+ static Rect<T> intersection(const Rect<T>& a, const Rect<T>& b)
+ {
+ Rect<T> r = a;
+ r.intersect(b);
+ return r;
+ }
+
+ Rect<T> intersected(const Rect<T>& other) const
+ {
+ return intersection(*this, other);
+ }
+
+ Rect<T> united(const Rect<T>&) const;
+
+ Point<T> top_left() const { return { left(), top() }; }
+ Point<T> top_right() const { return { right(), top() }; }
+ Point<T> bottom_left() const { return { left(), bottom() }; }
+ Point<T> bottom_right() const { return { right(), bottom() }; }
+
+ void align_within(const Rect<T>&, TextAlignment);
+
+ void center_within(const Rect<T>& other)
+ {
+ center_horizontally_within(other);
+ center_vertically_within(other);
+ }
+
+ void center_horizontally_within(const Rect<T>& other)
+ {
+ set_x(other.center().x() - width() / 2);
+ }
+
+ void center_vertically_within(const Rect<T>& other)
+ {
+ set_y(other.center().y() - height() / 2);
+ }
+
+ template<typename U>
+ Rect<U> to() const
+ {
+ return Rect<U>(*this);
+ }
+
+ String to_string() const;
+
+private:
+ Point<T> m_location;
+ Size<T> m_size;
+};
+
+template<typename T>
+const LogStream& operator<<(const LogStream& stream, const Rect<T>& rect)
+{
+ return stream << rect.to_string();
+}
+
+using IntRect = Rect<int>;
+using FloatRect = Rect<float>;
+
+ALWAYS_INLINE IntRect enclosing_int_rect(const FloatRect& float_rect)
+{
+ return {
+ (int)float_rect.x(),
+ (int)float_rect.y(),
+ (int)ceilf(float_rect.width()),
+ (int)ceilf(float_rect.height()),
+ };
+}
+
+}
+
+namespace AK {
+
+template<typename T>
+struct Formatter<Gfx::Rect<T>> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Rect<T>& value)
+ {
+ Formatter<StringView>::format(builder, value.to_string());
+ }
+};
+
+}
+
+namespace IPC {
+
+bool decode(Decoder&, Gfx::IntRect&);
+bool encode(Encoder&, const Gfx::IntRect&);
+
+}
diff --git a/Userland/Libraries/LibGfx/ShareableBitmap.cpp b/Userland/Libraries/LibGfx/ShareableBitmap.cpp
new file mode 100644
index 0000000000..6413140827
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ShareableBitmap.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ShareableBitmap.h>
+#include <LibGfx/Size.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+ShareableBitmap::ShareableBitmap(const Bitmap& bitmap)
+ : m_bitmap(bitmap.to_bitmap_backed_by_shared_buffer())
+{
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::ShareableBitmap& shareable_bitmap)
+{
+ encoder << shareable_bitmap.shbuf_id();
+ encoder << shareable_bitmap.width();
+ encoder << shareable_bitmap.height();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::ShareableBitmap& shareable_bitmap)
+{
+ i32 shbuf_id = 0;
+ Gfx::IntSize size;
+ if (!decoder.decode(shbuf_id))
+ return false;
+ if (!decoder.decode(size))
+ return false;
+
+ if (shbuf_id == -1)
+ return true;
+
+ dbgln("Decoding a ShareableBitmap with shbuf_id={}, size={}", shbuf_id, size);
+
+ auto shared_buffer = SharedBuffer::create_from_shbuf_id(shbuf_id);
+ if (!shared_buffer)
+ return false;
+
+ auto bitmap = Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, shared_buffer.release_nonnull(), size);
+ shareable_bitmap = bitmap->to_shareable_bitmap();
+ return true;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/ShareableBitmap.h b/Userland/Libraries/LibGfx/ShareableBitmap.h
new file mode 100644
index 0000000000..69ed9e7c78
--- /dev/null
+++ b/Userland/Libraries/LibGfx/ShareableBitmap.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Size.h>
+
+namespace Gfx {
+
+class ShareableBitmap {
+public:
+ ShareableBitmap() { }
+ explicit ShareableBitmap(const Gfx::Bitmap&);
+
+ bool is_valid() const { return m_bitmap; }
+
+ i32 shbuf_id() const { return m_bitmap ? m_bitmap->shbuf_id() : -1; }
+
+ const Bitmap* bitmap() const { return m_bitmap; }
+ Bitmap* bitmap() { return m_bitmap; }
+
+ IntSize size() const { return m_bitmap ? m_bitmap->size() : IntSize(); }
+ IntRect rect() const { return m_bitmap ? m_bitmap->rect() : IntRect(); }
+
+ int width() const { return size().width(); }
+ int height() const { return size().height(); }
+
+private:
+ RefPtr<Bitmap> m_bitmap;
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::ShareableBitmap&);
+bool decode(Decoder&, Gfx::ShareableBitmap&);
+
+}
diff --git a/Userland/Libraries/LibGfx/Size.cpp b/Userland/Libraries/LibGfx/Size.cpp
new file mode 100644
index 0000000000..e2cc6151ff
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Size.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGfx/Size.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace Gfx {
+
+template<>
+String IntSize::to_string() const
+{
+ return String::formatted("[{}x{}]", m_width, m_height);
+}
+
+template<>
+String FloatSize::to_string() const
+{
+ return String::formatted("[{}x{}]", m_width, m_height);
+}
+
+}
+
+namespace IPC {
+
+bool encode(Encoder& encoder, const Gfx::IntSize& size)
+{
+ encoder << size.width() << size.height();
+ return true;
+}
+
+bool decode(Decoder& decoder, Gfx::IntSize& size)
+{
+ int width = 0;
+ int height = 0;
+ if (!decoder.decode(width))
+ return false;
+ if (!decoder.decode(height))
+ return false;
+ size = { width, height };
+ return true;
+}
+
+}
+
+template class Gfx::Size<int>;
+template class Gfx::Size<float>;
diff --git a/Userland/Libraries/LibGfx/Size.h b/Userland/Libraries/LibGfx/Size.h
new file mode 100644
index 0000000000..7b0cbe96fc
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Size.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <LibGfx/Orientation.h>
+#include <LibIPC/Forward.h>
+
+namespace Gfx {
+
+template<typename T>
+class Size {
+public:
+ Size() { }
+
+ Size(T w, T h)
+ : m_width(w)
+ , m_height(h)
+ {
+ }
+
+ template<typename U>
+ Size(U width, U height)
+ : m_width(width)
+ , m_height(height)
+ {
+ }
+
+ template<typename U>
+ explicit Size(const Size<U>& other)
+ : m_width(other.width())
+ , m_height(other.height())
+ {
+ }
+
+ bool is_null() const { return !m_width && !m_height; }
+ bool is_empty() const { return m_width <= 0 || m_height <= 0; }
+
+ T width() const { return m_width; }
+ T height() const { return m_height; }
+
+ T area() const { return width() * height(); }
+
+ void set_width(T w) { m_width = w; }
+ void set_height(T h) { m_height = h; }
+
+ template<typename U>
+ bool contains(const Size<U>& other) const
+ {
+ return other.m_width <= m_width && other.m_height <= m_height;
+ }
+
+ bool operator==(const Size<T>& other) const
+ {
+ return m_width == other.m_width && m_height == other.m_height;
+ }
+
+ bool operator!=(const Size<T>& other) const
+ {
+ return !(*this == other);
+ }
+
+ Size<T>& operator-=(const Size<T>& other)
+ {
+ m_width -= other.m_width;
+ m_height -= other.m_height;
+ return *this;
+ }
+
+ Size<T>& operator+=(const Size<T>& other)
+ {
+ m_width += other.m_width;
+ m_height += other.m_height;
+ return *this;
+ }
+
+ Size<T> operator*(T factor) const { return { m_width * factor, m_height * factor }; }
+
+ Size<T>& operator*=(T factor)
+ {
+ m_width *= factor;
+ m_height *= factor;
+ return *this;
+ }
+
+ T primary_size_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? height() : width();
+ }
+
+ void set_primary_size_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_height(value);
+ } else {
+ set_width(value);
+ }
+ }
+
+ T secondary_size_for_orientation(Orientation orientation) const
+ {
+ return orientation == Orientation::Vertical ? width() : height();
+ }
+
+ void set_secondary_size_for_orientation(Orientation orientation, T value)
+ {
+ if (orientation == Orientation::Vertical) {
+ set_width(value);
+ } else {
+ set_height(value);
+ }
+ }
+
+ template<typename U>
+ Size<U> to_type() const
+ {
+ return Size<U>(*this);
+ }
+
+ String to_string() const;
+
+private:
+ T m_width { 0 };
+ T m_height { 0 };
+};
+
+template<typename T>
+const LogStream& operator<<(const LogStream& stream, const Gfx::Size<T>& size)
+{
+ return stream << size.to_string();
+}
+
+using IntSize = Size<int>;
+using FloatSize = Size<float>;
+
+}
+
+namespace AK {
+
+template<typename T>
+struct Formatter<Gfx::Size<T>> : Formatter<StringView> {
+ void format(FormatBuilder& builder, const Gfx::Size<T>& value)
+ {
+ Formatter<StringView>::format(builder, value.to_string());
+ }
+};
+
+}
+
+namespace IPC {
+
+bool encode(Encoder&, const Gfx::IntSize&);
+bool decode(Decoder&, Gfx::IntSize&);
+
+}
diff --git a/Userland/Libraries/LibGfx/StandardCursor.h b/Userland/Libraries/LibGfx/StandardCursor.h
new file mode 100644
index 0000000000..38c35b7076
--- /dev/null
+++ b/Userland/Libraries/LibGfx/StandardCursor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+enum class StandardCursor {
+ None = 0,
+ Hidden,
+ Arrow,
+ Crosshair,
+ IBeam,
+ ResizeHorizontal,
+ ResizeVertical,
+ ResizeDiagonalTLBR,
+ ResizeDiagonalBLTR,
+ ResizeColumn,
+ ResizeRow,
+ Hand,
+ Help,
+ Drag,
+ Move,
+ Wait,
+ __Count,
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Streamer.h b/Userland/Libraries/LibGfx/Streamer.h
new file mode 100644
index 0000000000..475726585b
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Streamer.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Hüseyin ASLITÜRK <asliturk@hotmail.com>, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Array.h>
+#include <AK/Endian.h>
+#include <AK/Types.h>
+#include <string.h>
+
+namespace Gfx {
+
+class Streamer {
+public:
+ constexpr Streamer(const u8* data, size_t size)
+ : m_data_ptr(data)
+ , m_size_remaining(size)
+ {
+ }
+
+ template<typename T>
+ constexpr bool read(T& value)
+ {
+ AK::Array<u8, sizeof(T)> network_buffer {};
+ auto network_value = new (network_buffer.data()) AK::NetworkOrdered<T> {};
+ auto res = read_bytes(network_buffer.data(), sizeof(T));
+ value = T(*network_value);
+ return res;
+ }
+
+ constexpr bool read_bytes(u8* buffer, size_t count)
+ {
+ if (m_size_remaining < count) {
+ return false;
+ }
+ memcpy(buffer, m_data_ptr, count);
+ m_data_ptr += count;
+ m_size_remaining -= count;
+ return true;
+ }
+
+ constexpr bool at_end() const { return !m_size_remaining; }
+
+ constexpr void step_back()
+ {
+ m_data_ptr -= 1;
+ m_size_remaining += 1;
+ }
+
+private:
+ const u8* m_data_ptr { nullptr };
+ size_t m_size_remaining { 0 };
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/StylePainter.cpp b/Userland/Libraries/LibGfx/StylePainter.cpp
new file mode 100644
index 0000000000..ad830958d8
--- /dev/null
+++ b/Userland/Libraries/LibGfx/StylePainter.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/ClassicStylePainter.h>
+#include <LibGfx/Painter.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+namespace Gfx {
+
+BaseStylePainter& StylePainter::current()
+{
+ static ClassicStylePainter style;
+ return style;
+}
+
+void StylePainter::paint_tab_button(Painter& painter, const IntRect& rect, const Palette& palette, bool active, bool hovered, bool enabled, bool top)
+{
+ current().paint_tab_button(painter, rect, palette, active, hovered, enabled, top);
+}
+
+void StylePainter::paint_button(Painter& painter, const IntRect& rect, const Palette& palette, ButtonStyle button_style, bool pressed, bool hovered, bool checked, bool enabled, bool focused)
+{
+ current().paint_button(painter, rect, palette, button_style, pressed, hovered, checked, enabled, focused);
+}
+
+void StylePainter::paint_surface(Painter& painter, const IntRect& rect, const Palette& palette, bool paint_vertical_lines, bool paint_top_line)
+{
+ current().paint_surface(painter, rect, palette, paint_vertical_lines, paint_top_line);
+}
+
+void StylePainter::paint_frame(Painter& painter, const IntRect& rect, const Palette& palette, FrameShape shape, FrameShadow shadow, int thickness, bool skip_vertical_lines)
+{
+ current().paint_frame(painter, rect, palette, shape, shadow, thickness, skip_vertical_lines);
+}
+
+void StylePainter::paint_window_frame(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ current().paint_window_frame(painter, rect, palette);
+}
+
+void StylePainter::paint_progress_bar(Painter& painter, const IntRect& rect, const Palette& palette, int min, int max, int value, const StringView& text)
+{
+ current().paint_progress_bar(painter, rect, palette, min, max, value, text);
+}
+
+void StylePainter::paint_radio_button(Painter& painter, const IntRect& rect, const Palette& palette, bool is_checked, bool is_being_pressed)
+{
+ current().paint_radio_button(painter, rect, palette, is_checked, is_being_pressed);
+}
+
+void StylePainter::paint_check_box(Painter& painter, const IntRect& rect, const Palette& palette, bool is_enabled, bool is_checked, bool is_being_pressed)
+{
+ current().paint_check_box(painter, rect, palette, is_enabled, is_checked, is_being_pressed);
+}
+
+void StylePainter::paint_transparency_grid(Painter& painter, const IntRect& rect, const Palette& palette)
+{
+ current().paint_transparency_grid(painter, rect, palette);
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/StylePainter.h b/Userland/Libraries/LibGfx/StylePainter.h
new file mode 100644
index 0000000000..779241c252
--- /dev/null
+++ b/Userland/Libraries/LibGfx/StylePainter.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+enum class ButtonStyle {
+ Normal,
+ CoolBar
+};
+enum class FrameShadow {
+ Plain,
+ Raised,
+ Sunken
+};
+enum class FrameShape {
+ NoFrame,
+ Box,
+ Container,
+ Panel,
+ VerticalLine,
+ HorizontalLine
+};
+
+// FIXME: should this be in its own header?
+class BaseStylePainter {
+public:
+ virtual ~BaseStylePainter() { }
+
+ virtual void paint_button(Painter&, const IntRect&, const Palette&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true, bool focused = false) = 0;
+ virtual void paint_tab_button(Painter&, const IntRect&, const Palette&, bool active, bool hovered, bool enabled, bool top) = 0;
+ virtual void paint_surface(Painter&, const IntRect&, const Palette&, bool paint_vertical_lines = true, bool paint_top_line = true) = 0;
+ virtual void paint_frame(Painter&, const IntRect&, const Palette&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false) = 0;
+ virtual void paint_window_frame(Painter&, const IntRect&, const Palette&) = 0;
+ virtual void paint_progress_bar(Painter&, const IntRect&, const Palette&, int min, int max, int value, const StringView& text) = 0;
+ virtual void paint_radio_button(Painter&, const IntRect&, const Palette&, bool is_checked, bool is_being_pressed) = 0;
+ virtual void paint_check_box(Painter&, const IntRect&, const Palette&, bool is_enabled, bool is_checked, bool is_being_pressed) = 0;
+ virtual void paint_transparency_grid(Painter&, const IntRect&, const Palette&) = 0;
+
+protected:
+ BaseStylePainter() { }
+};
+
+class StylePainter {
+public:
+ static BaseStylePainter& current();
+
+ // FIXME: These are here for API compatibility, we should probably remove them and move BaseStylePainter into here
+ static void paint_button(Painter&, const IntRect&, const Palette&, ButtonStyle, bool pressed, bool hovered = false, bool checked = false, bool enabled = true, bool focused = false);
+ static void paint_tab_button(Painter&, const IntRect&, const Palette&, bool active, bool hovered, bool enabled, bool top);
+ static void paint_surface(Painter&, const IntRect&, const Palette&, bool paint_vertical_lines = true, bool paint_top_line = true);
+ static void paint_frame(Painter&, const IntRect&, const Palette&, FrameShape, FrameShadow, int thickness, bool skip_vertical_lines = false);
+ static void paint_window_frame(Painter&, const IntRect&, const Palette&);
+ static void paint_progress_bar(Painter&, const IntRect&, const Palette&, int min, int max, int value, const StringView& text);
+ static void paint_radio_button(Painter&, const IntRect&, const Palette&, bool is_checked, bool is_being_pressed);
+ static void paint_check_box(Painter&, const IntRect&, const Palette&, bool is_enabled, bool is_checked, bool is_being_pressed);
+ static void paint_transparency_grid(Painter&, const IntRect&, const Palette&);
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/SystemTheme.cpp b/Userland/Libraries/LibGfx/SystemTheme.cpp
new file mode 100644
index 0000000000..5ccb04256d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/SystemTheme.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/SharedBuffer.h>
+#include <LibCore/ConfigFile.h>
+#include <LibGfx/SystemTheme.h>
+#include <string.h>
+
+namespace Gfx {
+
+static SystemTheme dummy_theme;
+static const SystemTheme* theme_page = &dummy_theme;
+static RefPtr<SharedBuffer> theme_buffer;
+
+const SystemTheme& current_system_theme()
+{
+ ASSERT(theme_page);
+ return *theme_page;
+}
+
+int current_system_theme_buffer_id()
+{
+ ASSERT(theme_buffer);
+ return theme_buffer->shbuf_id();
+}
+
+void set_system_theme(SharedBuffer& buffer)
+{
+ theme_buffer = buffer;
+ theme_page = theme_buffer->data<SystemTheme>();
+}
+
+RefPtr<SharedBuffer> load_system_theme(const String& path)
+{
+ auto file = Core::ConfigFile::open(path);
+ auto buffer = SharedBuffer::create_with_size(sizeof(SystemTheme));
+
+ auto* data = buffer->data<SystemTheme>();
+
+ auto get_color = [&](auto& name) {
+ auto color_string = file->read_entry("Colors", name);
+ auto color = Color::from_string(color_string);
+ if (!color.has_value())
+ return Color(Color::Black);
+ return color.value();
+ };
+
+ auto get_metric = [&](auto& name, auto role) {
+ int metric = file->read_num_entry("Metrics", name, -1);
+ if (metric == -1) {
+ switch (role) {
+ case (int)MetricRole::TitleHeight:
+ return 19;
+ case (int)MetricRole::TitleButtonHeight:
+ return 15;
+ case (int)MetricRole::TitleButtonWidth:
+ return 15;
+ default:
+ dbgln("Metric {} has no fallback value!", name);
+ return 16;
+ }
+ }
+ return metric;
+ };
+
+ auto get_path = [&](auto& name, auto role) {
+ auto path = file->read_entry("Paths", name);
+ if (path.is_empty()) {
+ switch (role) {
+ case (int)PathRole::TitleButtonIcons:
+ return "/res/icons/16x16/";
+ default:
+ return "/res/";
+ }
+ }
+ return &path[0];
+ };
+
+#undef __ENUMERATE_COLOR_ROLE
+#define __ENUMERATE_COLOR_ROLE(role) \
+ data->color[(int)ColorRole::role] = get_color(#role).value();
+ ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
+#undef __ENUMERATE_COLOR_ROLE
+
+#define DO_METRIC(x) \
+ data->metric[(int)MetricRole::x] = get_metric(#x, (int)MetricRole::x)
+
+ DO_METRIC(TitleHeight);
+ DO_METRIC(TitleButtonWidth);
+ DO_METRIC(TitleButtonHeight);
+
+#define DO_PATH(x) \
+ do { \
+ auto path = get_path(#x, (int)PathRole::x); \
+ memcpy(data->path[(int)PathRole::x], path, min(strlen(path) + 1, sizeof(data->path[(int)PathRole::x]))); \
+ data->path[(int)PathRole::x][sizeof(data->path[(int)PathRole::x]) - 1] = '\0'; \
+ } while (0)
+
+ DO_PATH(TitleButtonIcons);
+
+ buffer->seal();
+ buffer->share_globally();
+
+ return buffer;
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/SystemTheme.h b/Userland/Libraries/LibGfx/SystemTheme.h
new file mode 100644
index 0000000000..60e382c65f
--- /dev/null
+++ b/Userland/Libraries/LibGfx/SystemTheme.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibGfx/Color.h>
+
+namespace Gfx {
+
+#define ENUMERATE_COLOR_ROLES(C) \
+ C(ActiveLink) \
+ C(ActiveWindowBorder1) \
+ C(ActiveWindowBorder2) \
+ C(ActiveWindowTitle) \
+ C(ActiveWindowTitleShadow) \
+ C(ActiveWindowTitleStripes) \
+ C(Base) \
+ C(BaseText) \
+ C(Button) \
+ C(ButtonText) \
+ C(DesktopBackground) \
+ C(FocusOutline) \
+ C(HighlightWindowBorder1) \
+ C(HighlightWindowBorder2) \
+ C(HighlightWindowTitle) \
+ C(HighlightWindowTitleShadow) \
+ C(HighlightWindowTitleStripes) \
+ C(HighlightSearching) \
+ C(HighlightSearchingText) \
+ C(HoverHighlight) \
+ C(InactiveSelection) \
+ C(InactiveSelectionText) \
+ C(InactiveWindowBorder1) \
+ C(InactiveWindowBorder2) \
+ C(InactiveWindowTitle) \
+ C(InactiveWindowTitleShadow) \
+ C(InactiveWindowTitleStripes) \
+ C(Link) \
+ C(MenuBase) \
+ C(MenuBaseText) \
+ C(MenuSelection) \
+ C(MenuSelectionText) \
+ C(MenuStripe) \
+ C(MovingWindowBorder1) \
+ C(MovingWindowBorder2) \
+ C(MovingWindowTitle) \
+ C(MovingWindowTitleShadow) \
+ C(MovingWindowTitleStripes) \
+ C(PlaceholderText) \
+ C(RubberBandBorder) \
+ C(RubberBandFill) \
+ C(Ruler) \
+ C(RulerActiveText) \
+ C(RulerBorder) \
+ C(RulerInactiveText) \
+ C(Selection) \
+ C(SelectionText) \
+ C(SyntaxComment) \
+ C(SyntaxControlKeyword) \
+ C(SyntaxIdentifier) \
+ C(SyntaxKeyword) \
+ C(SyntaxNumber) \
+ C(SyntaxOperator) \
+ C(SyntaxPreprocessorStatement) \
+ C(SyntaxPreprocessorValue) \
+ C(SyntaxPunctuation) \
+ C(SyntaxString) \
+ C(SyntaxType) \
+ C(TextCursor) \
+ C(ThreedHighlight) \
+ C(ThreedShadow1) \
+ C(ThreedShadow2) \
+ C(Tooltip) \
+ C(TooltipText) \
+ C(VisitedLink) \
+ C(Window) \
+ C(WindowText)
+
+enum class ColorRole {
+ NoRole,
+
+#undef __ENUMERATE_COLOR_ROLE
+#define __ENUMERATE_COLOR_ROLE(role) role,
+ ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
+#undef __ENUMERATE_COLOR_ROLE
+
+ __Count,
+
+ Background = Window,
+ DisabledText = ThreedShadow1,
+};
+
+inline const char* to_string(ColorRole role)
+{
+ switch (role) {
+ case ColorRole::NoRole:
+ return "NoRole";
+#undef __ENUMERATE_COLOR_ROLE
+#define __ENUMERATE_COLOR_ROLE(role) \
+ case ColorRole::role: \
+ return #role;
+ ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
+#undef __ENUMERATE_COLOR_ROLE
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+enum class MetricRole {
+ NoRole,
+ TitleHeight,
+ TitleButtonWidth,
+ TitleButtonHeight,
+ __Count,
+};
+
+enum class PathRole {
+ NoRole,
+ TitleButtonIcons,
+ __Count,
+};
+
+struct SystemTheme {
+ RGBA32 color[(int)ColorRole::__Count];
+ int metric[(int)MetricRole::__Count];
+ char path[(int)PathRole::__Count][256]; // TODO: PATH_MAX?
+};
+
+const SystemTheme& current_system_theme();
+int current_system_theme_buffer_id();
+void set_system_theme(SharedBuffer&);
+RefPtr<SharedBuffer> load_system_theme(const String& path);
+
+}
+
+using Gfx::ColorRole;
diff --git a/Userland/Libraries/LibGfx/TextAlignment.h b/Userland/Libraries/LibGfx/TextAlignment.h
new file mode 100644
index 0000000000..5faea7f480
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TextAlignment.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/StringView.h>
+
+namespace Gfx {
+
+#define GFX_ENUMERATE_TEXT_ALIGNMENTS(M) \
+ M(TopLeft) \
+ M(CenterLeft) \
+ M(Center) \
+ M(CenterRight) \
+ M(TopRight) \
+ M(BottomRight)
+
+enum class TextAlignment {
+#define __ENUMERATE(x) x,
+ GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
+};
+
+inline bool is_right_text_alignment(TextAlignment alignment)
+{
+ switch (alignment) {
+ case TextAlignment::CenterRight:
+ case TextAlignment::TopRight:
+ case TextAlignment::BottomRight:
+ return true;
+ default:
+ return false;
+ }
+}
+
+inline bool is_vertically_centered_text_alignment(TextAlignment alignment)
+{
+ switch (alignment) {
+ case TextAlignment::CenterLeft:
+ case TextAlignment::CenterRight:
+ case TextAlignment::Center:
+ return true;
+ default:
+ return false;
+ }
+}
+
+inline Optional<TextAlignment> text_alignment_from_string(const StringView& string)
+{
+#define __ENUMERATE(x) \
+ if (string == #x) \
+ return TextAlignment::x;
+ GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
+ return {};
+}
+
+inline const char* to_string(TextAlignment text_alignment)
+{
+#define __ENUMERATE(x) \
+ if (text_alignment == TextAlignment::x) \
+ return #x;
+ GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/TextAttributes.h b/Userland/Libraries/LibGfx/TextAttributes.h
new file mode 100644
index 0000000000..1de9e5c01d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TextAttributes.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <LibGfx/Color.h>
+
+namespace Gfx {
+
+struct TextAttributes {
+ Color color;
+ Optional<Color> background_color;
+ bool underline { false };
+ bool bold { false };
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/TextElision.h b/Userland/Libraries/LibGfx/TextElision.h
new file mode 100644
index 0000000000..cf8cecc001
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TextElision.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace Gfx {
+
+enum class TextElision {
+ None,
+ Right,
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Triangle.cpp b/Userland/Libraries/LibGfx/Triangle.cpp
new file mode 100644
index 0000000000..d326b59f1c
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Triangle.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/String.h>
+#include <LibGfx/Triangle.h>
+
+namespace Gfx {
+
+String Triangle::to_string() const
+{
+ return String::formatted("({},{},{})", m_a, m_b, m_c);
+}
+
+const LogStream& operator<<(const LogStream& stream, const Triangle& value)
+{
+ return stream << value.to_string();
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/Triangle.h b/Userland/Libraries/LibGfx/Triangle.h
new file mode 100644
index 0000000000..7d8e69e80d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Triangle.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Point.h>
+
+namespace Gfx {
+
+class Triangle {
+public:
+ Triangle(IntPoint a, IntPoint b, IntPoint c)
+ : m_a(a)
+ , m_b(b)
+ , m_c(c)
+ {
+ m_det = (m_b.x() - m_a.x()) * (m_c.y() - m_a.y()) - (m_b.y() - m_a.y()) * (m_c.x() - m_a.x());
+ }
+
+ IntPoint a() const { return m_a; }
+ IntPoint b() const { return m_b; }
+ IntPoint c() const { return m_c; }
+
+ bool contains(IntPoint p) const
+ {
+ int x = p.x();
+ int y = p.y();
+
+ int ax = m_a.x();
+ int bx = m_b.x();
+ int cx = m_c.x();
+
+ int ay = m_a.y();
+ int by = m_b.y();
+ int cy = m_c.y();
+
+ if (m_det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) <= 0)
+ return false;
+ if (m_det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) <= 0)
+ return false;
+ if (m_det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) <= 0)
+ return false;
+ return true;
+ }
+
+ String to_string() const;
+
+private:
+ int m_det;
+ IntPoint m_a;
+ IntPoint m_b;
+ IntPoint m_c;
+};
+
+const LogStream& operator<<(const LogStream&, const Triangle&);
+
+}
diff --git a/Userland/Libraries/LibGfx/Vector3.h b/Userland/Libraries/LibGfx/Vector3.h
new file mode 100644
index 0000000000..0a93e5075d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/Vector3.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <math.h>
+
+namespace Gfx {
+template<typename T>
+class Vector3 {
+public:
+ Vector3() = default;
+ Vector3(T x, T y, T z)
+ : m_x(x)
+ , m_y(y)
+ , m_z(z)
+ {
+ }
+
+ T x() const { return m_x; }
+ T y() const { return m_y; }
+ T z() const { return m_z; }
+
+ void set_x(T value) { m_x = value; }
+ void set_y(T value) { m_y = value; }
+ void set_z(T value) { m_z = value; }
+
+ Vector3 operator+(const Vector3& other) const
+ {
+ return Vector3(m_x + other.m_x, m_y + other.m_y, m_z + other.m_z);
+ }
+
+ Vector3 operator-(const Vector3& other) const
+ {
+ return Vector3(m_x - other.m_x, m_y - other.m_y, m_z - other.m_z);
+ }
+
+ Vector3 operator*(T f) const
+ {
+ return Vector3(m_x * f, m_y * f, m_z * f);
+ }
+
+ Vector3 operator/(T f) const
+ {
+ return Vector3(m_x / f, m_y / f, m_z / f);
+ }
+
+ T dot(const Vector3& other) const
+ {
+ return m_x * other.m_x + m_y * other.m_y + m_z * other.m_z;
+ }
+
+ Vector3 cross(const Vector3& other) const
+ {
+ return Vector3(
+ m_y * other.m_z - m_z * other.m_y,
+ m_z * other.m_x - m_x * other.m_z,
+ m_x * other.m_y - m_y * other.m_x);
+ }
+
+ Vector3 normalized() const
+ {
+ T inv_length = 1 / length();
+ return *this * inv_length;
+ }
+
+ Vector3 clamped(T m, T x) const
+ {
+ Vector3 copy { *this };
+ copy.clamp(m, x);
+ return copy;
+ }
+
+ void clamp(T min_value, T max_value)
+ {
+ m_x = max(min_value, m_x);
+ m_y = max(min_value, m_y);
+ m_z = max(min_value, m_z);
+ m_x = min(max_value, m_x);
+ m_y = min(max_value, m_y);
+ m_z = min(max_value, m_z);
+ }
+
+ void normalize()
+ {
+ T inv_length = 1 / length();
+ m_x *= inv_length;
+ m_y *= inv_length;
+ m_z *= inv_length;
+ }
+
+ T length() const
+ {
+ return sqrt(m_x * m_x + m_y * m_y + m_z * m_z);
+ }
+
+private:
+ T m_x;
+ T m_y;
+ T m_z;
+};
+
+typedef Vector3<float> FloatVector3;
+typedef Vector3<double> DoubleVector3;
+
+}
+
+using Gfx::DoubleVector3;
+using Gfx::FloatVector3;
+using Gfx::Vector3;
diff --git a/Userland/Libraries/LibGfx/WindowTheme.cpp b/Userland/Libraries/LibGfx/WindowTheme.cpp
new file mode 100644
index 0000000000..dddf8d0630
--- /dev/null
+++ b/Userland/Libraries/LibGfx/WindowTheme.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibGfx/ClassicWindowTheme.h>
+#include <LibGfx/WindowTheme.h>
+
+namespace Gfx {
+
+WindowTheme& WindowTheme::current()
+{
+ static ClassicWindowTheme theme;
+ return theme;
+}
+
+WindowTheme::~WindowTheme()
+{
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/WindowTheme.h b/Userland/Libraries/LibGfx/WindowTheme.h
new file mode 100644
index 0000000000..8ae0174723
--- /dev/null
+++ b/Userland/Libraries/LibGfx/WindowTheme.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibGfx/Forward.h>
+
+namespace Gfx {
+
+class WindowTheme {
+public:
+ enum class WindowType {
+ Normal,
+ Notification,
+ Other,
+ };
+
+ enum class WindowState {
+ Active,
+ Inactive,
+ Highlighted,
+ Moving,
+ };
+
+ virtual ~WindowTheme();
+
+ static WindowTheme& current();
+
+ virtual void paint_normal_frame(Painter&, WindowState, const IntRect& window_rect, const StringView& title, const Bitmap& icon, const Palette&, const IntRect& leftmost_button_rect) const = 0;
+ virtual void paint_notification_frame(Painter&, const IntRect& window_rect, const Palette&, const IntRect& close_button_rect) const = 0;
+
+ virtual int title_bar_height(const Palette&) const = 0;
+ virtual IntRect title_bar_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+ virtual IntRect title_bar_icon_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+ virtual IntRect title_bar_text_rect(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+
+ virtual IntRect frame_rect_for_window(WindowType, const IntRect& window_rect, const Palette&) const = 0;
+
+ virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const = 0;
+
+protected:
+ WindowTheme() { }
+};
+
+}