/src/serenity/Userland/Libraries/LibGfx/Bitmap.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2018-2024, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/Bitmap.h> |
9 | | #include <AK/ByteString.h> |
10 | | #include <AK/Checked.h> |
11 | | #include <AK/LexicalPath.h> |
12 | | #include <AK/Memory.h> |
13 | | #include <AK/MemoryStream.h> |
14 | | #include <AK/Optional.h> |
15 | | #include <AK/Queue.h> |
16 | | #include <AK/ScopeGuard.h> |
17 | | #include <AK/Try.h> |
18 | | #include <LibCore/File.h> |
19 | | #include <LibCore/MappedFile.h> |
20 | | #include <LibCore/MimeData.h> |
21 | | #include <LibCore/System.h> |
22 | | #include <LibGfx/Bitmap.h> |
23 | | #include <LibGfx/ImageFormats/ImageDecoder.h> |
24 | | #include <LibGfx/ShareableBitmap.h> |
25 | | #include <LibIPC/Decoder.h> |
26 | | #include <LibIPC/Encoder.h> |
27 | | #include <LibIPC/File.h> |
28 | | #include <errno.h> |
29 | | #include <stdio.h> |
30 | | |
31 | | namespace Gfx { |
32 | | |
33 | | struct BackingStore { |
34 | | void* data { nullptr }; |
35 | | size_t pitch { 0 }; |
36 | | size_t size_in_bytes { 0 }; |
37 | | }; |
38 | | |
39 | | size_t Bitmap::minimum_pitch(size_t physical_width, BitmapFormat format) |
40 | 615k | { |
41 | 615k | size_t element_size; |
42 | 615k | switch (determine_storage_format(format)) { |
43 | 21.0k | case StorageFormat::BGRx8888: |
44 | 615k | case StorageFormat::BGRA8888: |
45 | 615k | case StorageFormat::RGBA8888: |
46 | 615k | element_size = 4; |
47 | 615k | break; |
48 | 0 | default: |
49 | 0 | VERIFY_NOT_REACHED(); |
50 | 615k | } |
51 | | |
52 | 615k | return physical_width * element_size; |
53 | 615k | } |
54 | | |
55 | | static bool size_would_overflow(BitmapFormat format, IntSize size, int scale_factor) |
56 | 410k | { |
57 | 410k | if (size.width() < 0 || size.height() < 0) |
58 | 0 | return true; |
59 | | // This check is a bit arbitrary, but should protect us from most shenanigans: |
60 | 410k | if (size.width() >= INT16_MAX || size.height() >= INT16_MAX || scale_factor < 1 || scale_factor > 4) |
61 | 742 | return true; |
62 | | // In contrast, this check is absolutely necessary: |
63 | 410k | size_t pitch = Bitmap::minimum_pitch(size.width() * scale_factor, format); |
64 | 410k | return Checked<size_t>::multiplication_would_overflow(pitch, size.height() * scale_factor); |
65 | 410k | } |
66 | | |
67 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, IntSize size, int scale_factor, Optional<size_t> pitch) |
68 | 206k | { |
69 | 206k | auto backing_store = TRY(Bitmap::allocate_backing_store(format, size, scale_factor, pitch)); |
70 | 0 | return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, size, scale_factor, backing_store)); |
71 | 206k | } |
72 | | |
73 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, IntSize size, int scale_factor) |
74 | 0 | { |
75 | 0 | if (size_would_overflow(format, size, scale_factor)) |
76 | 0 | return Error::from_string_literal("Gfx::Bitmap::create_shareable size overflow"); |
77 | | |
78 | 0 | auto const pitch = minimum_pitch(size.width() * scale_factor, format); |
79 | 0 | auto const data_size = size_in_bytes(pitch, size.height() * scale_factor); |
80 | |
|
81 | 0 | auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(data_size, PAGE_SIZE))); |
82 | 0 | auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(format, buffer, size, scale_factor)); |
83 | 0 | return bitmap; |
84 | 0 | } |
85 | | |
86 | | Bitmap::Bitmap(BitmapFormat format, IntSize size, int scale_factor, BackingStore const& backing_store) |
87 | 205k | : m_size(size) |
88 | 205k | , m_scale(scale_factor) |
89 | 205k | , m_data(backing_store.data) |
90 | 205k | , m_pitch(backing_store.pitch) |
91 | 205k | , m_format(format) |
92 | 205k | { |
93 | 205k | VERIFY(!m_size.is_empty()); |
94 | 205k | VERIFY(!size_would_overflow(format, size, scale_factor)); |
95 | 205k | VERIFY(m_data); |
96 | 205k | VERIFY(backing_store.size_in_bytes == size_in_bytes()); |
97 | 205k | m_destruction_callback = [data = m_data, size_in_bytes = this->size_in_bytes()] { |
98 | 205k | kfree_sized(data, size_in_bytes); |
99 | 205k | }; |
100 | 205k | } |
101 | | |
102 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_wrapper(BitmapFormat format, IntSize size, int scale_factor, size_t pitch, void* data, Function<void()>&& destruction_callback) |
103 | 0 | { |
104 | 0 | if (size_would_overflow(format, size, scale_factor)) |
105 | 0 | return Error::from_string_literal("Gfx::Bitmap::create_wrapper size overflow"); |
106 | 0 | return adopt_ref(*new Bitmap(format, size, scale_factor, pitch, data, move(destruction_callback))); |
107 | 0 | } |
108 | | |
109 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::load_from_file(StringView path, int scale_factor, Optional<IntSize> ideal_size) |
110 | 0 | { |
111 | 0 | if (scale_factor > 1 && path.starts_with("/res/"sv)) { |
112 | 0 | auto load_scaled_bitmap = [](StringView path, int scale_factor, Optional<IntSize> ideal_size) -> ErrorOr<NonnullRefPtr<Bitmap>> { |
113 | 0 | LexicalPath lexical_path { path }; |
114 | 0 | StringBuilder highdpi_icon_path; |
115 | 0 | TRY(highdpi_icon_path.try_appendff("{}/{}-{}x.{}", lexical_path.dirname(), lexical_path.title(), scale_factor, lexical_path.extension())); |
116 | | |
117 | 0 | auto highdpi_icon_string = highdpi_icon_path.string_view(); |
118 | 0 | auto file = TRY(Core::File::open(highdpi_icon_string, Core::File::OpenMode::Read)); |
119 | | |
120 | 0 | auto bitmap = TRY(load_from_file(move(file), highdpi_icon_string, ideal_size)); |
121 | 0 | if (bitmap->width() % scale_factor != 0 || bitmap->height() % scale_factor != 0) |
122 | 0 | return Error::from_string_literal("Bitmap::load_from_file: HighDPI image size should be divisible by scale factor"); |
123 | 0 | bitmap->m_size.set_width(bitmap->width() / scale_factor); |
124 | 0 | bitmap->m_size.set_height(bitmap->height() / scale_factor); |
125 | 0 | bitmap->m_scale = scale_factor; |
126 | 0 | return bitmap; |
127 | 0 | }; |
128 | |
|
129 | 0 | auto scaled_bitmap_or_error = load_scaled_bitmap(path, scale_factor, ideal_size); |
130 | 0 | if (!scaled_bitmap_or_error.is_error()) |
131 | 0 | return scaled_bitmap_or_error.release_value(); |
132 | | |
133 | 0 | auto error = scaled_bitmap_or_error.release_error(); |
134 | 0 | if (!(error.is_syscall() && error.code() == ENOENT)) { |
135 | 0 | dbgln("Couldn't load scaled bitmap: {}", error); |
136 | 0 | dbgln("Trying base scale instead."); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | 0 | auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read)); |
141 | 0 | return load_from_file(move(file), path, ideal_size); |
142 | 0 | } |
143 | | |
144 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::load_from_file(NonnullOwnPtr<Core::File> file, StringView path, Optional<IntSize> ideal_size) |
145 | 0 | { |
146 | 0 | auto mapped_file = TRY(Core::MappedFile::map_from_file(move(file), path)); |
147 | 0 | auto mime_type = Core::guess_mime_type_based_on_filename(path); |
148 | 0 | return load_from_bytes(mapped_file->bytes(), ideal_size, mime_type); |
149 | 0 | } |
150 | | |
151 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::load_from_bytes(ReadonlyBytes bytes, Optional<IntSize> ideal_size, Optional<ByteString> mine_type) |
152 | 0 | { |
153 | 0 | if (auto decoder = TRY(ImageDecoder::try_create_for_raw_bytes(bytes, mine_type))) { |
154 | 0 | auto frame = TRY(decoder->frame(0, ideal_size)); |
155 | 0 | if (auto& bitmap = frame.image) |
156 | 0 | return bitmap.release_nonnull(); |
157 | 0 | } |
158 | | |
159 | 0 | return Error::from_string_literal("Gfx::Bitmap unable to load from file"); |
160 | 0 | } |
161 | | |
162 | | Bitmap::Bitmap(BitmapFormat format, IntSize size, int scale_factor, size_t pitch, void* data, Function<void()>&& destruction_callback) |
163 | 0 | : m_size(size) |
164 | 0 | , m_scale(scale_factor) |
165 | 0 | , m_data(data) |
166 | 0 | , m_pitch(pitch) |
167 | 0 | , m_format(format) |
168 | 0 | , m_destruction_callback(move(destruction_callback)) |
169 | 0 | { |
170 | 0 | VERIFY(pitch >= minimum_pitch(size.width() * scale_factor, format)); |
171 | 0 | VERIFY(!size_would_overflow(format, size, scale_factor)); |
172 | | // FIXME: assert that `data` is actually long enough! |
173 | 0 | } |
174 | | |
175 | | static bool check_size(IntSize size, int scale_factor, BitmapFormat format, unsigned actual_size) |
176 | 0 | { |
177 | | // FIXME: Code duplication of size_in_bytes() and m_pitch |
178 | 0 | unsigned expected_size_min = Bitmap::minimum_pitch(size.width() * scale_factor, format) * size.height() * scale_factor; |
179 | 0 | unsigned expected_size_max = round_up_to_power_of_two(expected_size_min, PAGE_SIZE); |
180 | 0 | if (expected_size_min > actual_size || actual_size > expected_size_max) { |
181 | | // Getting here is most likely an error. |
182 | 0 | dbgln("Constructing a shared bitmap for format {} and size {} @ {}x, which demands {} bytes, which rounds up to at most {}.", |
183 | 0 | static_cast<int>(format), |
184 | 0 | size, |
185 | 0 | scale_factor, |
186 | 0 | expected_size_min, |
187 | 0 | expected_size_max); |
188 | |
|
189 | 0 | dbgln("However, we were given {} bytes, which is outside this range?! Refusing cowardly.", actual_size); |
190 | 0 | return false; |
191 | 0 | } |
192 | 0 | return true; |
193 | 0 | } |
194 | | |
195 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat format, Core::AnonymousBuffer buffer, IntSize size, int scale_factor) |
196 | 0 | { |
197 | 0 | if (size_would_overflow(format, size, scale_factor)) |
198 | 0 | return Error::from_string_literal("Gfx::Bitmap::create_with_anonymous_buffer size overflow"); |
199 | | |
200 | 0 | return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, move(buffer), size, scale_factor)); |
201 | 0 | } |
202 | | |
203 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_from_serialized_byte_buffer(ByteBuffer&& buffer) |
204 | 0 | { |
205 | 0 | return create_from_serialized_bytes(buffer.bytes()); |
206 | 0 | } |
207 | | |
208 | | /// Read a bitmap as described by: |
209 | | /// - actual size |
210 | | /// - width |
211 | | /// - height |
212 | | /// - scale_factor |
213 | | /// - format |
214 | | /// - image data (= actual size * u8) |
215 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_from_serialized_bytes(ReadonlyBytes bytes) |
216 | 0 | { |
217 | 0 | FixedMemoryStream stream { bytes }; |
218 | |
|
219 | 0 | auto actual_size = TRY(stream.read_value<size_t>()); |
220 | 0 | auto width = TRY(stream.read_value<unsigned>()); |
221 | 0 | auto height = TRY(stream.read_value<unsigned>()); |
222 | 0 | auto scale_factor = TRY(stream.read_value<unsigned>()); |
223 | 0 | auto format = TRY(stream.read_value<BitmapFormat>()); |
224 | | |
225 | 0 | if (format > BitmapFormat::LastValid || format < BitmapFormat::FirstValid) |
226 | 0 | return Error::from_string_literal("Gfx::Bitmap::create_from_serialized_byte_buffer: decode failed"); |
227 | | |
228 | 0 | if (!check_size({ width, height }, scale_factor, format, actual_size)) |
229 | 0 | return Error::from_string_literal("Gfx::Bitmap::create_from_serialized_byte_buffer: decode failed"); |
230 | | |
231 | 0 | if (TRY(stream.size()) - TRY(stream.tell()) < actual_size) |
232 | 0 | return Error::from_string_literal("Gfx::Bitmap::create_from_serialized_byte_buffer: decode failed"); |
233 | | |
234 | 0 | auto data = bytes.slice(TRY(stream.tell()), actual_size); |
235 | | |
236 | 0 | auto bitmap = TRY(Bitmap::create(format, { width, height }, scale_factor)); |
237 | | |
238 | 0 | data.copy_to({ bitmap->scanline(0), bitmap->size_in_bytes() }); |
239 | 0 | return bitmap; |
240 | 0 | } |
241 | | |
242 | | ErrorOr<ByteBuffer> Bitmap::serialize_to_byte_buffer() const |
243 | 0 | { |
244 | 0 | auto buffer = TRY(ByteBuffer::create_uninitialized(sizeof(size_t) + 3 * sizeof(unsigned) + sizeof(BitmapFormat) + size_in_bytes())); |
245 | 0 | FixedMemoryStream stream { buffer.span() }; |
246 | |
|
247 | 0 | TRY(stream.write_value(size_in_bytes())); |
248 | 0 | TRY(stream.write_value<unsigned>(size().width())); |
249 | 0 | TRY(stream.write_value<unsigned>(size().height())); |
250 | 0 | TRY(stream.write_value<unsigned>(scale())); |
251 | 0 | TRY(stream.write_value(m_format)); |
252 | | |
253 | 0 | auto size = size_in_bytes(); |
254 | 0 | TRY(stream.write_until_depleted({ scanline(0), size })); |
255 | | |
256 | 0 | VERIFY(TRY(stream.tell()) == TRY(stream.size())); |
257 | | |
258 | 0 | return buffer; |
259 | 0 | } |
260 | | |
261 | | Bitmap::Bitmap(BitmapFormat format, Core::AnonymousBuffer buffer, IntSize size, int scale_factor) |
262 | 0 | : m_size(size) |
263 | 0 | , m_scale(scale_factor) |
264 | 0 | , m_data(buffer.data<void>()) |
265 | 0 | , m_pitch(minimum_pitch(size.width() * scale_factor, format)) |
266 | 0 | , m_format(format) |
267 | 0 | , m_buffer(move(buffer)) |
268 | 0 | { |
269 | 0 | VERIFY(!size_would_overflow(format, size, scale_factor)); |
270 | 0 | } |
271 | | |
272 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::clone() const |
273 | 183k | { |
274 | 183k | auto new_bitmap = TRY(Bitmap::create(format(), size(), scale())); |
275 | | |
276 | 183k | VERIFY(size_in_bytes() == new_bitmap->size_in_bytes()); |
277 | 183k | memcpy(new_bitmap->scanline(0), scanline(0), size_in_bytes()); |
278 | | |
279 | 183k | return new_bitmap; |
280 | 183k | } |
281 | | |
282 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::rotated(Gfx::RotationDirection rotation_direction) const |
283 | 0 | { |
284 | 0 | if (rotation_direction == Gfx::RotationDirection::Flip) { |
285 | 0 | auto new_bitmap = TRY(Gfx::Bitmap::create(format(), { width(), height() }, scale())); |
286 | | |
287 | 0 | auto w = this->physical_width(); |
288 | 0 | auto h = this->physical_height(); |
289 | 0 | for (int i = 0; i < w; i++) { |
290 | 0 | for (int j = 0; j < h; j++) |
291 | 0 | new_bitmap->set_pixel(w - i - 1, h - j - 1, this->get_pixel(i, j)); |
292 | 0 | } |
293 | |
|
294 | 0 | return new_bitmap; |
295 | 0 | } |
296 | | |
297 | 0 | auto new_bitmap = TRY(Gfx::Bitmap::create(this->format(), { height(), width() }, scale())); |
298 | | |
299 | 0 | auto w = this->physical_width(); |
300 | 0 | auto h = this->physical_height(); |
301 | 0 | for (int i = 0; i < w; i++) { |
302 | 0 | for (int j = 0; j < h; j++) { |
303 | 0 | Color color; |
304 | 0 | if (rotation_direction == Gfx::RotationDirection::CounterClockwise) |
305 | 0 | color = this->get_pixel(w - i - 1, j); |
306 | 0 | else |
307 | 0 | color = this->get_pixel(i, h - j - 1); |
308 | |
|
309 | 0 | new_bitmap->set_pixel(j, i, color); |
310 | 0 | } |
311 | 0 | } |
312 | |
|
313 | 0 | return new_bitmap; |
314 | 0 | } |
315 | | |
316 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::flipped(Gfx::Orientation orientation) const |
317 | 0 | { |
318 | 0 | auto new_bitmap = TRY(Gfx::Bitmap::create(this->format(), { width(), height() }, scale())); |
319 | | |
320 | 0 | auto w = this->physical_width(); |
321 | 0 | auto h = this->physical_height(); |
322 | 0 | for (int i = 0; i < w; i++) { |
323 | 0 | for (int j = 0; j < h; j++) { |
324 | 0 | Color color = this->get_pixel(i, j); |
325 | 0 | if (orientation == Orientation::Vertical) |
326 | 0 | new_bitmap->set_pixel(i, h - j - 1, color); |
327 | 0 | else |
328 | 0 | new_bitmap->set_pixel(w - i - 1, j, color); |
329 | 0 | } |
330 | 0 | } |
331 | |
|
332 | 0 | return new_bitmap; |
333 | 0 | } |
334 | | |
335 | | void Bitmap::apply_mask(Gfx::Bitmap const& mask, MaskKind mask_kind) |
336 | 0 | { |
337 | 0 | VERIFY(size() == mask.size()); |
338 | | |
339 | 0 | for (int y = 0; y < height(); y++) { |
340 | 0 | for (int x = 0; x < width(); x++) { |
341 | 0 | auto color = get_pixel(x, y); |
342 | 0 | auto mask_color = mask.get_pixel(x, y); |
343 | 0 | if (mask_kind == MaskKind::Luminance) { |
344 | 0 | color = color.with_alpha(color.alpha() * mask_color.alpha() * mask_color.luminosity() / (255 * 255)); |
345 | 0 | } else { |
346 | 0 | VERIFY(mask_kind == MaskKind::Alpha); |
347 | 0 | color = color.with_alpha(color.alpha() * mask_color.alpha() / 255); |
348 | 0 | } |
349 | 0 | set_pixel(x, y, color); |
350 | 0 | } |
351 | 0 | } |
352 | 0 | } |
353 | | |
354 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled(int sx, int sy) const |
355 | 0 | { |
356 | 0 | VERIFY(sx >= 0 && sy >= 0); |
357 | 0 | if (sx == 1 && sy == 1) |
358 | 0 | return clone(); |
359 | | |
360 | 0 | auto new_bitmap = TRY(Gfx::Bitmap::create(format(), { width() * sx, height() * sy }, scale())); |
361 | | |
362 | 0 | auto old_width = physical_width(); |
363 | 0 | auto old_height = physical_height(); |
364 | |
|
365 | 0 | for (int y = 0; y < old_height; y++) { |
366 | 0 | for (int x = 0; x < old_width; x++) { |
367 | 0 | auto color = get_pixel(x, y); |
368 | |
|
369 | 0 | auto base_x = x * sx; |
370 | 0 | auto base_y = y * sy; |
371 | 0 | for (int new_y = base_y; new_y < base_y + sy; new_y++) { |
372 | 0 | for (int new_x = base_x; new_x < base_x + sx; new_x++) { |
373 | 0 | new_bitmap->set_pixel(new_x, new_y, color); |
374 | 0 | } |
375 | 0 | } |
376 | 0 | } |
377 | 0 | } |
378 | |
|
379 | 0 | return new_bitmap; |
380 | 0 | } |
381 | | |
382 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled(float sx, float sy) const |
383 | 0 | { |
384 | 0 | VERIFY(sx >= 0.0f && sy >= 0.0f); |
385 | 0 | if (floorf(sx) == sx && floorf(sy) == sy) |
386 | 0 | return scaled(static_cast<int>(sx), static_cast<int>(sy)); |
387 | | |
388 | 0 | int scaled_width = (int)ceilf(sx * (float)width()); |
389 | 0 | int scaled_height = (int)ceilf(sy * (float)height()); |
390 | 0 | return scaled_to_size({ scaled_width, scaled_height }); |
391 | 0 | } |
392 | | |
393 | | // http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html |
394 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled_to_size(Gfx::IntSize size) const |
395 | 0 | { |
396 | 0 | auto new_bitmap = TRY(Gfx::Bitmap::create(format(), size, scale())); |
397 | | |
398 | 0 | auto old_width = physical_width(); |
399 | 0 | auto old_height = physical_height(); |
400 | 0 | auto new_width = new_bitmap->physical_width(); |
401 | 0 | auto new_height = new_bitmap->physical_height(); |
402 | |
|
403 | 0 | if (old_width == 1 && old_height == 1) { |
404 | 0 | new_bitmap->fill(get_pixel(0, 0)); |
405 | 0 | return new_bitmap; |
406 | 0 | } |
407 | | |
408 | 0 | if (old_width > 1 && old_height > 1) { |
409 | | // The interpolation goes out of bounds on the bottom- and right-most edges. |
410 | | // We handle those in two specialized loops not only to make them faster, but |
411 | | // also to avoid four branch checks for every pixel. |
412 | 0 | for (int y = 0; y < new_height - 1; y++) { |
413 | 0 | for (int x = 0; x < new_width - 1; x++) { |
414 | 0 | auto p = static_cast<float>(x) * static_cast<float>(old_width - 1) / static_cast<float>(new_width - 1); |
415 | 0 | auto q = static_cast<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); |
416 | |
|
417 | 0 | int i = floorf(p); |
418 | 0 | int j = floorf(q); |
419 | 0 | float u = p - static_cast<float>(i); |
420 | 0 | float v = q - static_cast<float>(j); |
421 | |
|
422 | 0 | auto a = get_pixel(i, j); |
423 | 0 | auto b = get_pixel(i + 1, j); |
424 | 0 | auto c = get_pixel(i, j + 1); |
425 | 0 | auto d = get_pixel(i + 1, j + 1); |
426 | |
|
427 | 0 | auto e = a.mixed_with(b, u); |
428 | 0 | auto f = c.mixed_with(d, u); |
429 | 0 | auto color = e.mixed_with(f, v); |
430 | 0 | new_bitmap->set_pixel(x, y, color); |
431 | 0 | } |
432 | 0 | } |
433 | | |
434 | | // Bottom strip (excluding last pixel) |
435 | 0 | auto old_bottom_y = old_height - 1; |
436 | 0 | auto new_bottom_y = new_height - 1; |
437 | 0 | for (int x = 0; x < new_width - 1; x++) { |
438 | 0 | auto p = static_cast<float>(x) * static_cast<float>(old_width - 1) / static_cast<float>(new_width - 1); |
439 | |
|
440 | 0 | int i = floorf(p); |
441 | 0 | float u = p - static_cast<float>(i); |
442 | |
|
443 | 0 | auto a = get_pixel(i, old_bottom_y); |
444 | 0 | auto b = get_pixel(i + 1, old_bottom_y); |
445 | 0 | auto color = a.mixed_with(b, u); |
446 | 0 | new_bitmap->set_pixel(x, new_bottom_y, color); |
447 | 0 | } |
448 | | |
449 | | // Right strip (excluding last pixel) |
450 | 0 | auto old_right_x = old_width - 1; |
451 | 0 | auto new_right_x = new_width - 1; |
452 | 0 | for (int y = 0; y < new_height - 1; y++) { |
453 | 0 | auto q = static_cast<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); |
454 | |
|
455 | 0 | int j = floorf(q); |
456 | 0 | float v = q - static_cast<float>(j); |
457 | |
|
458 | 0 | auto c = get_pixel(old_right_x, j); |
459 | 0 | auto d = get_pixel(old_right_x, j + 1); |
460 | |
|
461 | 0 | auto color = c.mixed_with(d, v); |
462 | 0 | new_bitmap->set_pixel(new_right_x, y, color); |
463 | 0 | } |
464 | | |
465 | | // Bottom-right pixel |
466 | 0 | new_bitmap->set_pixel(new_width - 1, new_height - 1, get_pixel(physical_width() - 1, physical_height() - 1)); |
467 | 0 | return new_bitmap; |
468 | 0 | } else if (old_height == 1) { |
469 | | // Copy horizontal strip multiple times (excluding last pixel to out of bounds). |
470 | 0 | auto old_bottom_y = old_height - 1; |
471 | 0 | for (int x = 0; x < new_width - 1; x++) { |
472 | 0 | auto p = static_cast<float>(x) * static_cast<float>(old_width - 1) / static_cast<float>(new_width - 1); |
473 | 0 | int i = floorf(p); |
474 | 0 | float u = p - static_cast<float>(i); |
475 | |
|
476 | 0 | auto a = get_pixel(i, old_bottom_y); |
477 | 0 | auto b = get_pixel(i + 1, old_bottom_y); |
478 | 0 | auto color = a.mixed_with(b, u); |
479 | 0 | for (int new_bottom_y = 0; new_bottom_y < new_height; new_bottom_y++) { |
480 | | // Interpolate color only once and then copy into all columns. |
481 | 0 | new_bitmap->set_pixel(x, new_bottom_y, color); |
482 | 0 | } |
483 | 0 | } |
484 | 0 | for (int new_bottom_y = 0; new_bottom_y < new_height; new_bottom_y++) { |
485 | | // Copy last pixel of horizontal strip |
486 | 0 | new_bitmap->set_pixel(new_width - 1, new_bottom_y, get_pixel(physical_width() - 1, old_bottom_y)); |
487 | 0 | } |
488 | 0 | return new_bitmap; |
489 | 0 | } else if (old_width == 1) { |
490 | | // Copy vertical strip multiple times (excluding last pixel to avoid out of bounds). |
491 | 0 | auto old_right_x = old_width - 1; |
492 | 0 | for (int y = 0; y < new_height - 1; y++) { |
493 | 0 | auto q = static_cast<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); |
494 | 0 | int j = floorf(q); |
495 | 0 | float v = q - static_cast<float>(j); |
496 | |
|
497 | 0 | auto c = get_pixel(old_right_x, j); |
498 | 0 | auto d = get_pixel(old_right_x, j + 1); |
499 | |
|
500 | 0 | auto color = c.mixed_with(d, v); |
501 | 0 | for (int new_right_x = 0; new_right_x < new_width; new_right_x++) { |
502 | | // Interpolate color only once and copy into all rows. |
503 | 0 | new_bitmap->set_pixel(new_right_x, y, color); |
504 | 0 | } |
505 | 0 | } |
506 | 0 | for (int new_right_x = 0; new_right_x < new_width; new_right_x++) { |
507 | | // Copy last pixel of vertical strip |
508 | 0 | new_bitmap->set_pixel(new_right_x, new_height - 1, get_pixel(old_right_x, physical_height() - 1)); |
509 | 0 | } |
510 | 0 | } |
511 | 0 | return new_bitmap; |
512 | 0 | } |
513 | | |
514 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::cropped(Gfx::IntRect crop, Optional<BitmapFormat> new_bitmap_format) const |
515 | 226 | { |
516 | 226 | auto new_bitmap = TRY(Gfx::Bitmap::create(new_bitmap_format.value_or(format()), { crop.width(), crop.height() }, scale())); |
517 | 0 | auto scaled_crop = crop * scale(); |
518 | | |
519 | 101k | for (int y = 0; y < scaled_crop.height(); ++y) { |
520 | 5.97M | for (int x = 0; x < scaled_crop.width(); ++x) { |
521 | 5.87M | int global_x = x + scaled_crop.left(); |
522 | 5.87M | int global_y = y + scaled_crop.top(); |
523 | 5.87M | if (global_x >= physical_width() || global_y >= physical_height() || global_x < 0 || global_y < 0) { |
524 | 0 | new_bitmap->set_pixel(x, y, Gfx::Color::Black); |
525 | 5.87M | } else { |
526 | 5.87M | new_bitmap->set_pixel(x, y, get_pixel(global_x, global_y)); |
527 | 5.87M | } |
528 | 5.87M | } |
529 | 101k | } |
530 | 226 | return new_bitmap; |
531 | 226 | } |
532 | | |
533 | | ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::to_bitmap_backed_by_anonymous_buffer() const |
534 | 0 | { |
535 | 0 | if (m_buffer.is_valid()) { |
536 | | // FIXME: The const_cast here is awkward. |
537 | 0 | return NonnullRefPtr { const_cast<Bitmap&>(*this) }; |
538 | 0 | } |
539 | 0 | auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(size_in_bytes(), PAGE_SIZE))); |
540 | 0 | auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(m_format, move(buffer), size(), scale())); |
541 | 0 | memcpy(bitmap->scanline(0), scanline(0), size_in_bytes()); |
542 | 0 | return bitmap; |
543 | 0 | } |
544 | | |
545 | | ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::inverted() const |
546 | 0 | { |
547 | 0 | auto inverted_bitmap = TRY(clone()); |
548 | 0 | for (auto y = 0; y < height(); y++) { |
549 | 0 | for (auto x = 0; x < width(); x++) |
550 | 0 | inverted_bitmap->set_pixel(x, y, get_pixel(x, y).inverted()); |
551 | 0 | } |
552 | 0 | return inverted_bitmap; |
553 | 0 | } |
554 | | |
555 | | Bitmap::~Bitmap() |
556 | 205k | { |
557 | 205k | if (m_destruction_callback) |
558 | 205k | m_destruction_callback(); |
559 | 205k | m_data = nullptr; |
560 | 205k | } |
561 | | |
562 | | void Bitmap::strip_alpha_channel() |
563 | 310 | { |
564 | 310 | VERIFY(m_format == BitmapFormat::BGRA8888 || m_format == BitmapFormat::BGRx8888); |
565 | 310 | for (ARGB32& pixel : *this) |
566 | 1.41G | pixel = 0xff000000 | (pixel & 0xffffff); |
567 | 310 | m_format = BitmapFormat::BGRx8888; |
568 | 310 | } |
569 | | |
570 | | void Bitmap::fill(Color color) |
571 | 2.27k | { |
572 | 8.45M | for (int y = 0; y < physical_height(); ++y) { |
573 | 8.45M | auto* scanline = this->scanline(y); |
574 | 8.45M | fast_u32_fill(scanline, color.value(), physical_width()); |
575 | 8.45M | } |
576 | 2.27k | } |
577 | | |
578 | | Gfx::ShareableBitmap Bitmap::to_shareable_bitmap() const |
579 | 0 | { |
580 | 0 | auto bitmap_or_error = to_bitmap_backed_by_anonymous_buffer(); |
581 | 0 | if (bitmap_or_error.is_error()) |
582 | 0 | return {}; |
583 | 0 | return Gfx::ShareableBitmap { bitmap_or_error.release_value_but_fixme_should_propagate_errors(), Gfx::ShareableBitmap::ConstructWithKnownGoodBitmap }; |
584 | 0 | } |
585 | | |
586 | | ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSize size, int scale_factor, Optional<size_t> pitch) |
587 | 206k | { |
588 | 206k | if (size.is_empty()) |
589 | 184 | return Error::from_string_literal("Gfx::Bitmap backing store size is empty"); |
590 | | |
591 | 205k | if (size_would_overflow(format, size, scale_factor)) |
592 | 742 | return Error::from_string_literal("Gfx::Bitmap backing store size overflow"); |
593 | | |
594 | 205k | if (!pitch.has_value()) |
595 | 205k | pitch = minimum_pitch(size.width() * scale_factor, format); |
596 | 205k | auto const data_size_in_bytes = size_in_bytes(pitch.value(), size.height() * scale_factor); |
597 | | |
598 | 205k | void* data = kcalloc(1, data_size_in_bytes); |
599 | 205k | if (data == nullptr) |
600 | 0 | return Error::from_errno(errno); |
601 | 205k | return BackingStore { data, pitch.value(), data_size_in_bytes }; |
602 | 205k | } |
603 | | |
604 | | bool Bitmap::visually_equals(Bitmap const& other) const |
605 | 0 | { |
606 | 0 | auto own_width = width(); |
607 | 0 | auto own_height = height(); |
608 | 0 | if (other.width() != own_width || other.height() != own_height) |
609 | 0 | return false; |
610 | | |
611 | 0 | for (auto y = 0; y < own_height; ++y) { |
612 | 0 | for (auto x = 0; x < own_width; ++x) { |
613 | 0 | if (get_pixel(x, y) != other.get_pixel(x, y)) |
614 | 0 | return false; |
615 | 0 | } |
616 | 0 | } |
617 | | |
618 | 0 | return true; |
619 | 0 | } |
620 | | |
621 | | Optional<Color> Bitmap::solid_color(u8 alpha_threshold) const |
622 | 0 | { |
623 | 0 | Optional<Color> color; |
624 | 0 | for (auto y = 0; y < height(); ++y) { |
625 | 0 | for (auto x = 0; x < width(); ++x) { |
626 | 0 | auto const& pixel = get_pixel(x, y); |
627 | 0 | if (has_alpha_channel() && pixel.alpha() <= alpha_threshold) |
628 | 0 | continue; |
629 | 0 | if (!color.has_value()) |
630 | 0 | color = pixel; |
631 | 0 | else if (pixel != color) |
632 | 0 | return {}; |
633 | 0 | } |
634 | 0 | } |
635 | 0 | return color; |
636 | 0 | } |
637 | | |
638 | | void Bitmap::flood_visit_from_point(Gfx::IntPoint start_point, int threshold, |
639 | | Function<void(Gfx::IntPoint location)> pixel_reached) |
640 | 0 | { |
641 | |
|
642 | 0 | VERIFY(rect().contains(start_point)); |
643 | | |
644 | 0 | auto target_color = get_pixel(start_point.x(), start_point.y()); |
645 | |
|
646 | 0 | float threshold_normalized_squared = (threshold / 100.0f) * (threshold / 100.0f); |
647 | |
|
648 | 0 | Queue<Gfx::IntPoint> points_to_visit = Queue<Gfx::IntPoint>(); |
649 | |
|
650 | 0 | points_to_visit.enqueue(start_point); |
651 | 0 | pixel_reached(start_point); |
652 | 0 | auto flood_mask = AK::Bitmap::create(width() * height(), false).release_value_but_fixme_should_propagate_errors(); |
653 | |
|
654 | 0 | flood_mask.set(width() * start_point.y() + start_point.x(), true); |
655 | | |
656 | | // This implements a non-recursive flood fill. This is a breadth-first search of paintable neighbors |
657 | | // As we find neighbors that are reachable we call the location_reached callback, add them to the queue, and mark them in the mask |
658 | 0 | while (!points_to_visit.is_empty()) { |
659 | 0 | auto current_point = points_to_visit.dequeue(); |
660 | 0 | auto candidate_points = Array { |
661 | 0 | current_point.moved_left(1), |
662 | 0 | current_point.moved_right(1), |
663 | 0 | current_point.moved_up(1), |
664 | 0 | current_point.moved_down(1) |
665 | 0 | }; |
666 | 0 | for (auto candidate_point : candidate_points) { |
667 | 0 | auto flood_mask_index = width() * candidate_point.y() + candidate_point.x(); |
668 | 0 | if (!rect().contains(candidate_point)) |
669 | 0 | continue; |
670 | | |
671 | 0 | auto pixel_color = get_pixel<Gfx::StorageFormat::BGRA8888>(candidate_point.x(), candidate_point.y()); |
672 | 0 | auto can_paint = pixel_color.distance_squared_to(target_color) <= threshold_normalized_squared; |
673 | |
|
674 | 0 | if (flood_mask.get(flood_mask_index) == false && can_paint) { |
675 | 0 | points_to_visit.enqueue(candidate_point); |
676 | 0 | pixel_reached(candidate_point); |
677 | 0 | } |
678 | |
|
679 | 0 | flood_mask.set(flood_mask_index, true); |
680 | 0 | } |
681 | 0 | } |
682 | 0 | } |
683 | | |
684 | | } |
685 | | |
686 | | namespace IPC { |
687 | | |
688 | | template<> |
689 | | ErrorOr<void> encode(Encoder& encoder, AK::NonnullRefPtr<Gfx::Bitmap> const& bitmap) |
690 | 0 | { |
691 | 0 | Core::AnonymousBuffer buffer; |
692 | 0 | if (bitmap->anonymous_buffer().is_valid()) { |
693 | 0 | buffer = bitmap->anonymous_buffer(); |
694 | 0 | } else { |
695 | 0 | buffer = MUST(Core::AnonymousBuffer::create_with_size(bitmap->size_in_bytes())); |
696 | 0 | memcpy(buffer.data<void>(), bitmap->scanline(0), bitmap->size_in_bytes()); |
697 | 0 | } |
698 | 0 | TRY(encoder.encode(TRY(IPC::File::clone_fd(buffer.fd())))); |
699 | 0 | TRY(encoder.encode(static_cast<u32>(bitmap->format()))); |
700 | 0 | TRY(encoder.encode(bitmap->size_in_bytes())); |
701 | 0 | TRY(encoder.encode(bitmap->pitch())); |
702 | 0 | TRY(encoder.encode(bitmap->size())); |
703 | 0 | TRY(encoder.encode(bitmap->scale())); |
704 | 0 | return {}; |
705 | 0 | } |
706 | | |
707 | | template<> |
708 | | ErrorOr<AK::NonnullRefPtr<Gfx::Bitmap>> decode(Decoder& decoder) |
709 | 0 | { |
710 | 0 | auto anon_file = TRY(decoder.decode<IPC::File>()); |
711 | 0 | auto raw_bitmap_format = TRY(decoder.decode<u32>()); |
712 | 0 | if (!Gfx::is_valid_bitmap_format(raw_bitmap_format)) |
713 | 0 | return Error::from_string_literal("IPC: Invalid Gfx::ShareableBitmap format"); |
714 | 0 | auto bitmap_format = static_cast<Gfx::BitmapFormat>(raw_bitmap_format); |
715 | 0 | auto size_in_bytes = TRY(decoder.decode<size_t>()); |
716 | 0 | auto pitch = TRY(decoder.decode<size_t>()); |
717 | 0 | auto size = TRY(decoder.decode<Gfx::IntSize>()); |
718 | 0 | auto scale = TRY(decoder.decode<int>()); |
719 | 0 | auto* data = TRY(Core::System::mmap(nullptr, round_up_to_power_of_two(size_in_bytes, PAGE_SIZE), PROT_READ | PROT_WRITE, MAP_SHARED, anon_file.fd(), 0)); |
720 | 0 | return Gfx::Bitmap::create_wrapper(bitmap_format, size, scale, pitch, data, [data, size_in_bytes] { |
721 | 0 | MUST(Core::System::munmap(data, size_in_bytes)); |
722 | 0 | }); |
723 | 0 | } |
724 | | |
725 | | } |