/src/serenity/Userland/Libraries/LibGfx/ImageFormats/BilevelImage.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2025, Nico Weber <thakis@chromium.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/NonnullOwnPtr.h> |
8 | | #include <LibGfx/Bitmap.h> |
9 | | #include <LibGfx/ICC/TagTypes.h> |
10 | | #include <LibGfx/ICC/WellKnownProfiles.h> |
11 | | #include <LibGfx/ImageFormats/BilevelImage.h> |
12 | | |
13 | | namespace Gfx { |
14 | | |
15 | | ErrorOr<NonnullRefPtr<BilevelImage>> BilevelImage::create(size_t width, size_t height) |
16 | 0 | { |
17 | 0 | size_t pitch = ceil_div(width, static_cast<size_t>(8)); |
18 | 0 | auto bits = TRY(ByteBuffer::create_uninitialized(pitch * height)); |
19 | 0 | return adopt_nonnull_ref_or_enomem(new (nothrow) BilevelImage(move(bits), width, height, pitch)); |
20 | 0 | } |
21 | | |
22 | | ErrorOr<NonnullRefPtr<BilevelImage>> BilevelImage::create_from_byte_buffer(ByteBuffer bitmap, size_t width, size_t height) |
23 | 0 | { |
24 | 0 | size_t pitch = ceil_div(width, static_cast<size_t>(8)); |
25 | 0 | return adopt_nonnull_ref_or_enomem(new (nothrow) BilevelImage(move(bitmap), width, height, pitch)); |
26 | 0 | } |
27 | | |
28 | | void BilevelImage::fill(bool b) |
29 | 0 | { |
30 | 0 | u8 fill_byte = b ? 0xff : 0; |
31 | 0 | for (auto& byte : m_bits.bytes()) |
32 | 0 | byte = fill_byte; |
33 | 0 | } |
34 | | |
35 | | template<OneOf<BilevelImage, BilevelSubImage> InputType, BilevelImage::CompositionType operator_> |
36 | | void composite_onto(InputType const& in, BilevelImage& out, IntPoint position) |
37 | 0 | { |
38 | 0 | static constexpr auto combine = [](auto dst, auto src) -> decltype(dst) { |
39 | 0 | switch (operator_) { |
40 | 0 | case BilevelImage::CompositionType::Or: |
41 | 0 | return dst | src; |
42 | 0 | case BilevelImage::CompositionType::And: |
43 | 0 | return dst & src; |
44 | 0 | case BilevelImage::CompositionType::Xor: |
45 | 0 | return dst ^ src; |
46 | 0 | case BilevelImage::CompositionType::XNor: |
47 | | // Clang is not happy with using ~ on a bool, even if it's fine with our use case. |
48 | | if constexpr (SameAs<decltype(dst), bool>) |
49 | 0 | return !(dst ^ src); |
50 | | else |
51 | 0 | return ~(dst ^ src); |
52 | 0 | case BilevelImage::CompositionType::Replace: |
53 | 0 | return src; |
54 | 0 | } |
55 | 0 | VERIFY_NOT_REACHED(); |
56 | 0 | }; Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIhhEEDtfp_ES7_SD_ Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlS7_T0_E_clIbbEEDtfp_ES7_SD_ |
57 | |
|
58 | 0 | IntRect bitmap_rect { position, { in.width(), in.height() } }; |
59 | 0 | IntRect out_rect { { 0, 0 }, { out.width(), out.height() } }; |
60 | 0 | IntRect clip_rect = bitmap_rect.intersected(out_rect); |
61 | |
|
62 | 0 | for (int y = clip_rect.top(); y < clip_rect.bottom(); ++y) { |
63 | 0 | for (int x = clip_rect.left(); x < clip_rect.right(); ++x) { |
64 | 0 | bool const can_use_byte = [&]() { |
65 | 0 | bool condition = x % 8 == 0 && position.x() % 8 == 0 && clip_rect.right() - x > 8; |
66 | | if constexpr (IsSame<InputType, BilevelSubImage>) |
67 | 0 | condition &= in.m_active_rect.x() % 8 == 0; |
68 | 0 | return condition; |
69 | 0 | }(); Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlvE_clEv |
70 | |
|
71 | 0 | if (can_use_byte) { |
72 | 0 | auto const& src = [&]() { |
73 | | if constexpr (IsSame<InputType, BilevelImage>) |
74 | 0 | return in.m_bits[(y - position.y()) * in.m_pitch + (x - position.x()) / 8]; |
75 | | else |
76 | 0 | return in.m_source->m_bits[(y - position.y() + in.m_active_rect.y()) * in.m_source->m_pitch + (x - position.x() + in.m_active_rect.x()) / 8]; |
77 | 0 | }(); Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv Unexecuted instantiation: _ZZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEEENKUlvE0_clEv |
78 | 0 | auto& dst = out.m_bits[y * out.m_pitch + x / 8]; |
79 | 0 | dst = combine(dst, src); |
80 | 0 | x += 7; |
81 | 0 | } else { |
82 | 0 | bool src_bit = in.get_bit(x - position.x(), y - position.y()); |
83 | 0 | bool dst_bit = out.get_bit(x, y); |
84 | 0 | out.set_bit(x, y, combine(dst_bit, src_bit)); |
85 | 0 | } |
86 | 0 | } |
87 | 0 | } |
88 | 0 | } Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE0EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE1EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE2EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE3EEEvRKT_RS4_NS_5PointIiEE Unexecuted instantiation: _ZN3Gfx14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_LNS4_15CompositionTypeE4EEEvRKT_RS4_NS_5PointIiEE |
89 | | |
90 | | template<OneOf<BilevelImage, BilevelSubImage> InputType> |
91 | | static void composite_onto(InputType const& in, BilevelImage& out, IntPoint position, BilevelImage::CompositionType operator_) |
92 | 0 | { |
93 | 0 | switch (operator_) { |
94 | 0 | case BilevelImage::CompositionType::Or: |
95 | 0 | composite_onto<InputType, BilevelImage::CompositionType::Or>(in, out, position); |
96 | 0 | break; |
97 | 0 | case BilevelImage::CompositionType::And: |
98 | 0 | composite_onto<InputType, BilevelImage::CompositionType::And>(in, out, position); |
99 | 0 | break; |
100 | 0 | case BilevelImage::CompositionType::Xor: |
101 | 0 | composite_onto<InputType, BilevelImage::CompositionType::Xor>(in, out, position); |
102 | 0 | break; |
103 | 0 | case BilevelImage::CompositionType::XNor: |
104 | 0 | composite_onto<InputType, BilevelImage::CompositionType::XNor>(in, out, position); |
105 | 0 | break; |
106 | 0 | case BilevelImage::CompositionType::Replace: |
107 | 0 | composite_onto<InputType, BilevelImage::CompositionType::Replace>(in, out, position); |
108 | 0 | break; |
109 | 0 | } |
110 | 0 | } Unexecuted instantiation: BilevelImage.cpp:_ZN3GfxL14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES4_EEvRKT_RS4_NS_5PointIiEENS4_15CompositionTypeE Unexecuted instantiation: BilevelImage.cpp:_ZN3GfxL14composite_ontoITkN2AK8Concepts5OneOfINS_12BilevelImageENS_15BilevelSubImageEEES5_EEvRKT_RS4_NS_5PointIiEENS4_15CompositionTypeE |
111 | | |
112 | | void BilevelImage::composite_onto(BilevelImage& out, IntPoint position, CompositionType operator_) const |
113 | 0 | { |
114 | 0 | ::Gfx::composite_onto(*this, out, position, operator_); |
115 | 0 | } |
116 | | |
117 | | void BilevelSubImage::composite_onto(BilevelImage& out, IntPoint position, BilevelImage::CompositionType operator_) const |
118 | 0 | { |
119 | 0 | ::Gfx::composite_onto(*this, out, position, operator_); |
120 | 0 | } |
121 | | |
122 | | bool BilevelSubImage::operator==(BilevelSubImage const& other) const |
123 | 0 | { |
124 | 0 | if (width() != other.width() || height() != other.height()) |
125 | 0 | return false; |
126 | | |
127 | 0 | for (size_t y = 0; y < height(); ++y) { |
128 | 0 | for (size_t x = 0; x < width(); ++x) { |
129 | 0 | if (get_bit(x, y) != other.get_bit(x, y)) |
130 | 0 | return false; |
131 | 0 | } |
132 | 0 | } |
133 | 0 | return true; |
134 | 0 | } |
135 | | |
136 | | BilevelSubImage BilevelImage::subbitmap(Gfx::IntRect const& rect) const |
137 | 0 | { |
138 | 0 | VERIFY(rect.x() >= 0); |
139 | 0 | VERIFY(rect.width() >= 0); |
140 | 0 | VERIFY(static_cast<size_t>(rect.right()) <= width()); |
141 | | |
142 | 0 | VERIFY(rect.y() >= 0); |
143 | 0 | VERIFY(rect.height() >= 0); |
144 | 0 | VERIFY(static_cast<size_t>(rect.bottom()) <= height()); |
145 | | |
146 | 0 | return BilevelSubImage { *this, rect }; |
147 | 0 | } |
148 | | |
149 | | BilevelSubImage BilevelImage::as_subbitmap() const |
150 | 0 | { |
151 | 0 | return subbitmap(IntRect(0, 0, m_width, m_height)); |
152 | 0 | } |
153 | | |
154 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> BilevelImage::to_gfx_bitmap() const |
155 | 0 | { |
156 | 0 | auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { m_width, m_height })); |
157 | 0 | for (size_t y = 0; y < m_height; ++y) { |
158 | 0 | for (size_t x = 0; x < m_width; ++x) { |
159 | 0 | auto color = get_bit(x, y) ? Color::Black : Color::White; |
160 | 0 | bitmap->set_pixel(x, y, color); |
161 | 0 | } |
162 | 0 | } |
163 | 0 | return bitmap; |
164 | 0 | } |
165 | | |
166 | | ErrorOr<ByteBuffer> BilevelImage::to_byte_buffer() const |
167 | 0 | { |
168 | 0 | return ByteBuffer::copy(m_bits); |
169 | 0 | } |
170 | | |
171 | | BilevelImage::BilevelImage(ByteBuffer bits, size_t width, size_t height, size_t pitch) |
172 | 0 | : m_bits(move(bits)) |
173 | 0 | , m_width(width) |
174 | 0 | , m_height(height) |
175 | 0 | , m_pitch(pitch) |
176 | 0 | { |
177 | 0 | } |
178 | | |
179 | | static Array<u32, 256> compute_luminosity_histogram(ByteBuffer const& bitmap) |
180 | 0 | { |
181 | 0 | Array<u32, 256> histogram {}; |
182 | 0 | for (u8 value : bitmap.span()) |
183 | 0 | histogram[value]++; |
184 | 0 | return histogram; |
185 | 0 | } |
186 | | |
187 | | static u8 compute_otsu_threshold(Array<u32, 256> const& histogram) |
188 | 0 | { |
189 | | // https://en.wikipedia.org/wiki/Otsu%27s_method#Otsu's_method |
190 | | // All multiplied through with number of pixels, since p(i) * number_of_pixels == histogram[i] |
191 | | // and it all cancels out when just looking for the max, as far as I can tell. |
192 | |
|
193 | 0 | u32 histogram_sum = 0; |
194 | 0 | u32 mu_T = 0; |
195 | 0 | for (int i = 0; i < 256; ++i) { |
196 | 0 | histogram_sum += histogram[i]; |
197 | 0 | mu_T += i * histogram[i]; |
198 | 0 | } |
199 | |
|
200 | 0 | u32 sum_0 = 0; |
201 | 0 | u32 omega_0 = 0; |
202 | 0 | f32 max_inter_class_variance = 0; |
203 | 0 | u8 threshold = 0; |
204 | |
|
205 | 0 | for (int i = 0; i < 256; ++i) { |
206 | 0 | omega_0 += histogram[i]; |
207 | 0 | u32 omega_1 = histogram_sum - omega_0; |
208 | 0 | if (omega_0 == 0 || omega_1 == 0) |
209 | 0 | continue; |
210 | | |
211 | 0 | sum_0 += i * histogram[i]; |
212 | 0 | u32 sum_1 = mu_T - sum_0; |
213 | 0 | f32 mu_0 = static_cast<f32>(sum_0) / omega_0; |
214 | 0 | f32 mu_1 = static_cast<f32>(sum_1) / omega_1; |
215 | 0 | f32 inter_class_variance = static_cast<f32>(omega_0) * static_cast<f32>(omega_1) * (mu_0 - mu_1) * (mu_0 - mu_1); |
216 | 0 | if (inter_class_variance > max_inter_class_variance) { |
217 | 0 | threshold = i; |
218 | 0 | max_inter_class_variance = inter_class_variance; |
219 | 0 | } |
220 | 0 | } |
221 | 0 | return threshold; |
222 | 0 | } |
223 | | |
224 | | template<unsigned N> |
225 | | static constexpr Array<u32, (1 << N) * (1 << N)> make_bayer_matrix() |
226 | 0 | { |
227 | 0 | constexpr auto size = 1u << N; |
228 | 0 | Array<u32, size * size> result {}; |
229 | 0 | for (size_t i = 0; i < N; ++i) { |
230 | 0 | auto slice_size = 1u << i; |
231 | 0 | for (size_t y = 0; y < slice_size; ++y) { |
232 | 0 | for (size_t x = 0; x < slice_size; ++x) { |
233 | 0 | u32 v = result[y * size + x]; |
234 | 0 | result[y * size + x] = 4 * v; |
235 | 0 | result[y * size + x + slice_size] = 4 * v + 2; |
236 | 0 | result[(y + slice_size) * size + x] = 4 * v + 3; |
237 | 0 | result[(y + slice_size) * size + x + slice_size] = 4 * v + 1; |
238 | 0 | } |
239 | 0 | } |
240 | 0 | } |
241 | 0 | return result; |
242 | 0 | } Unexecuted instantiation: BilevelImage.cpp:AK::Array<unsigned int, ((1)<<(1u))*((1)<<(1u))> Gfx::make_bayer_matrix<1u>() Unexecuted instantiation: BilevelImage.cpp:AK::Array<unsigned int, ((1)<<(2u))*((1)<<(2u))> Gfx::make_bayer_matrix<2u>() Unexecuted instantiation: BilevelImage.cpp:AK::Array<unsigned int, ((1)<<(3u))*((1)<<(3u))> Gfx::make_bayer_matrix<3u>() |
243 | | static constexpr auto bayer_matrix_2x2 = make_bayer_matrix<1>(); |
244 | | static constexpr auto bayer_matrix_4x4 = make_bayer_matrix<2>(); |
245 | | static constexpr auto bayer_matrix_8x8 = make_bayer_matrix<3>(); |
246 | | |
247 | | ErrorOr<NonnullRefPtr<BilevelImage>> BilevelImage::create_from_bitmap(Gfx::Bitmap const& bitmap, DitheringAlgorithm dithering_algorithm) |
248 | 0 | { |
249 | 0 | bool input_is_bilevel = true; |
250 | 0 | for (auto pixel : bitmap) { |
251 | 0 | if (pixel != 0xFF000000 && pixel != 0xFFFFFFFF) { |
252 | 0 | input_is_bilevel = false; |
253 | 0 | break; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | if (input_is_bilevel) { |
257 | 0 | auto bilevel_image = TRY(BilevelImage::create(bitmap.width(), bitmap.height())); |
258 | 0 | for (int y = 0; y < bitmap.height(); ++y) { |
259 | 0 | for (int x = 0; x < bitmap.width(); ++x) { |
260 | 0 | auto color = bitmap.get_pixel(x, y); |
261 | 0 | bilevel_image->set_bit(x, y, color == Color::Black ? 1 : 0); |
262 | 0 | } |
263 | 0 | } |
264 | 0 | return bilevel_image; |
265 | 0 | } |
266 | | |
267 | 0 | auto gray_bitmap = TRY(ByteBuffer::create_uninitialized(bitmap.width() * bitmap.height())); |
268 | 0 | for (int y = 0, i = 0; y < bitmap.height(); ++y) { |
269 | 0 | for (int x = 0; x < bitmap.width(); ++x, ++i) { |
270 | 0 | auto color = bitmap.get_pixel(x, y); |
271 | 0 | gray_bitmap[i] = color.luminosity(); |
272 | 0 | } |
273 | 0 | } |
274 | |
|
275 | 0 | auto srgb_curve = TRY(Gfx::ICC::sRGB_curve()); |
276 | 0 | for (u8& v : gray_bitmap.span()) |
277 | 0 | v = round_to<u8>(srgb_curve->evaluate(v / 255.0f) * 255.0f); |
278 | | |
279 | | // For now, do global thresholding with Otsu's method. |
280 | | // https://en.wikipedia.org/wiki/Otsu%27s_method |
281 | | // FIXME: Add an option to use average as threshold instead of Otsu? |
282 | 0 | auto histogram = compute_luminosity_histogram(gray_bitmap); |
283 | 0 | u8 threshold = compute_otsu_threshold(histogram); |
284 | |
|
285 | 0 | auto bilevel_image = TRY(BilevelImage::create(bitmap.width(), bitmap.height())); |
286 | |
|
287 | 0 | switch (dithering_algorithm) { |
288 | 0 | case DitheringAlgorithm::None: |
289 | 0 | for (int y = 0, i = 0; y < bitmap.height(); ++y) { |
290 | 0 | for (int x = 0; x < bitmap.width(); ++x, ++i) { |
291 | 0 | bilevel_image->set_bit(x, y, gray_bitmap[i] > threshold ? 0 : 1); |
292 | 0 | } |
293 | 0 | } |
294 | 0 | break; |
295 | 0 | case DitheringAlgorithm::Bayer2x2: |
296 | 0 | case DitheringAlgorithm::Bayer4x4: |
297 | 0 | case DitheringAlgorithm::Bayer8x8: { |
298 | 0 | auto bayer_matrix = [&]() { |
299 | 0 | switch (dithering_algorithm) { |
300 | 0 | case DitheringAlgorithm::Bayer2x2: |
301 | 0 | return bayer_matrix_2x2.span(); |
302 | 0 | case DitheringAlgorithm::Bayer4x4: |
303 | 0 | return bayer_matrix_4x4.span(); |
304 | 0 | case DitheringAlgorithm::Bayer8x8: |
305 | 0 | return bayer_matrix_8x8.span(); |
306 | 0 | default: |
307 | 0 | VERIFY_NOT_REACHED(); |
308 | 0 | } |
309 | 0 | }(); |
310 | |
|
311 | 0 | auto n = static_cast<int>(sqrt(bayer_matrix.size())); |
312 | 0 | VERIFY(is_power_of_two(n)); |
313 | 0 | auto mask = n - 1; |
314 | | |
315 | | // A bayer matrix of dimension N has N x N +1 different states. First one |
316 | | // is an all black matrix, and then one more for each element turning white. |
317 | 0 | u32 number_of_states = n * n + 1; |
318 | |
|
319 | 0 | for (int y = 0, i = 0; y < bitmap.height(); ++y) { |
320 | 0 | for (int x = 0; x < bitmap.width(); ++x, ++i) { |
321 | 0 | u8 threshold = round_to<u8>((1 + bayer_matrix[(y & mask) * n + (x & mask)]) * 255.f / number_of_states); |
322 | 0 | bilevel_image->set_bit(x, y, gray_bitmap[i] > threshold ? 0 : 1); |
323 | 0 | } |
324 | 0 | } |
325 | 0 | break; |
326 | 0 | } |
327 | 0 | case DitheringAlgorithm::FloydSteinberg: { |
328 | 0 | struct Factor { |
329 | 0 | int offset; |
330 | 0 | u8 factor; |
331 | 0 | }; |
332 | 0 | auto factors = Array { |
333 | 0 | Factor { 1, 7 }, |
334 | 0 | Factor { -1 + bitmap.width(), 3 }, |
335 | 0 | Factor { 0 + bitmap.width(), 5 }, |
336 | 0 | Factor { 1 + bitmap.width(), 1 }, |
337 | 0 | }; |
338 | 0 | for (int y = 0, i = 0; y < bitmap.height(); ++y) { |
339 | 0 | for (int x = 0; x < bitmap.width(); ++x, ++i) { |
340 | 0 | u8 old_pixel = gray_bitmap[i]; |
341 | 0 | u8 new_pixel = old_pixel > threshold ? 255 : 0; |
342 | 0 | bilevel_image->set_bit(x, y, new_pixel == 255 ? 0 : 1); |
343 | 0 | int error = static_cast<int>(old_pixel) - static_cast<int>(new_pixel); |
344 | 0 | for (auto const& factor : factors) { |
345 | 0 | int index = i + factor.offset; |
346 | 0 | if (index >= static_cast<int>(gray_bitmap.size())) |
347 | 0 | continue; |
348 | 0 | int new_value = static_cast<int>(gray_bitmap[index]) + (error * factor.factor) / 16; |
349 | 0 | gray_bitmap[index] = clamp(new_value, 0, 255); |
350 | 0 | } |
351 | 0 | } |
352 | 0 | } |
353 | 0 | break; |
354 | 0 | } |
355 | 0 | } |
356 | 0 | return bilevel_image; |
357 | 0 | } |
358 | | |
359 | | } |
360 | | |
361 | | unsigned AK::Traits<Gfx::BilevelSubImage>::hash(Gfx::BilevelSubImage const& image) |
362 | 0 | { |
363 | 0 | unsigned hash = 0; |
364 | 0 | for (size_t y = 0; y < image.height(); ++y) { |
365 | 0 | for (size_t x = 0; x < image.width(); ++x) { |
366 | 0 | auto value_hash = Traits<bool>::hash(image.get_bit(x, y)); |
367 | 0 | hash = pair_int_hash(hash, value_hash); |
368 | 0 | } |
369 | 0 | } |
370 | 0 | return hash; |
371 | 0 | } |