Coverage Report

Created: 2026-07-25 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}