1 /*
2 * This file is part of EasyRPG Player.
3 *
4 * EasyRPG Player is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * EasyRPG Player is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Headers
19 #define _USE_MATH_DEFINES
20 #include <cmath>
21 #include <cstdlib>
22 #include <cstring>
23 #include <algorithm>
24 #include <iostream>
25 #include <unordered_map>
26
27 #include "utils.h"
28 #include "cache.h"
29 #include "bitmap.h"
30 #include "filefinder.h"
31 #include "options.h"
32 #include <lcf/data.h>
33 #include "output.h"
34 #include "image_xyz.h"
35 #include "image_bmp.h"
36 #include "image_png.h"
37 #include "transform.h"
38 #include "font.h"
39 #include "output.h"
40 #include "util_macro.h"
41 #include "bitmap_hslrgb.h"
42 #include <iostream>
43
Create(int width,int height,const Color & color)44 BitmapRef Bitmap::Create(int width, int height, const Color& color) {
45 BitmapRef surface = Bitmap::Create(width, height, true);
46 surface->Fill(color);
47 return surface;
48 }
49
Create(Filesystem_Stream::InputStream stream,bool transparent,uint32_t flags)50 BitmapRef Bitmap::Create(Filesystem_Stream::InputStream stream, bool transparent, uint32_t flags) {
51 BitmapRef bmp = std::make_shared<Bitmap>(std::move(stream), transparent, flags);
52
53 if (!bmp->pixels()) {
54 return BitmapRef();
55 }
56
57 return bmp;
58 }
59
Create(const uint8_t * data,unsigned bytes,bool transparent,uint32_t flags)60 BitmapRef Bitmap::Create(const uint8_t* data, unsigned bytes, bool transparent, uint32_t flags) {
61 BitmapRef bmp = std::make_shared<Bitmap>(data, bytes, transparent, flags);
62
63 if (!bmp->pixels()) {
64 return BitmapRef();
65 }
66
67 return bmp;
68 }
69
Create(Bitmap const & source,Rect const & src_rect,bool transparent)70 BitmapRef Bitmap::Create(Bitmap const& source, Rect const& src_rect, bool transparent) {
71 return std::make_shared<Bitmap>(source, src_rect, transparent);
72 }
73
Create(int width,int height,bool transparent,int)74 BitmapRef Bitmap::Create(int width, int height, bool transparent, int /* bpp */) {
75 return std::make_shared<Bitmap>(width, height, transparent);
76 }
77
Create(void * pixels,int width,int height,int pitch,const DynamicFormat & format)78 BitmapRef Bitmap::Create(void *pixels, int width, int height, int pitch, const DynamicFormat& format) {
79 return std::make_shared<Bitmap>(pixels, width, height, pitch, format);
80 }
81
Bitmap(int width,int height,bool transparent)82 Bitmap::Bitmap(int width, int height, bool transparent) {
83 format = (transparent ? pixel_format : opaque_pixel_format);
84 pixman_format = find_format(format);
85 Init(width, height, (void *) NULL);
86 }
87
Bitmap(void * pixels,int width,int height,int pitch,const DynamicFormat & _format)88 Bitmap::Bitmap(void *pixels, int width, int height, int pitch, const DynamicFormat& _format) {
89 format = _format;
90 pixman_format = find_format(format);
91 Init(width, height, pixels, pitch, false);
92 }
93
Bitmap(Filesystem_Stream::InputStream stream,bool transparent,uint32_t flags)94 Bitmap::Bitmap(Filesystem_Stream::InputStream stream, bool transparent, uint32_t flags) {
95 format = (transparent ? pixel_format : opaque_pixel_format);
96 pixman_format = find_format(format);
97
98 if (!stream) {
99 Output::Error("Couldn't read image file {}", stream.GetName());
100 return;
101 }
102
103 int w = 0;
104 int h = 0;
105 void* pixels = nullptr;
106
107 uint8_t data[4] = {};
108 size_t bytes = stream.read(reinterpret_cast<char*>(data), 4).gcount();
109 stream.seekg(0, std::ios::ios_base::beg);
110
111 bool img_okay = false;
112
113 if (bytes >= 4 && strncmp((char*)data, "XYZ1", 4) == 0)
114 img_okay = ImageXYZ::ReadXYZ(stream, transparent, w, h, pixels);
115 else if (bytes > 2 && strncmp((char*)data, "BM", 2) == 0)
116 img_okay = ImageBMP::ReadBMP(stream, transparent, w, h, pixels);
117 else if (bytes >= 4 && strncmp((char*)(data + 1), "PNG", 3) == 0)
118 img_okay = ImagePNG::ReadPNG(stream, transparent, w, h, pixels);
119 else
120 Output::Warning("Unsupported image file {} (Magic: {:02X})", stream.GetName(), *reinterpret_cast<uint32_t*>(data));
121
122 if (!img_okay) {
123 free(pixels);
124 pixels = nullptr;
125 return;
126 }
127
128 Init(w, h, nullptr);
129
130 ConvertImage(w, h, pixels, transparent);
131
132 CheckPixels(flags);
133
134 filename = ToString(stream.GetName());
135 }
136
Bitmap(const uint8_t * data,unsigned bytes,bool transparent,uint32_t flags)137 Bitmap::Bitmap(const uint8_t* data, unsigned bytes, bool transparent, uint32_t flags) {
138 format = (transparent ? pixel_format : opaque_pixel_format);
139 pixman_format = find_format(format);
140
141 int w = 0, h = 0;
142 void* pixels = nullptr;
143
144 bool img_okay = false;
145
146 if (bytes > 4 && strncmp((char*) data, "XYZ1", 4) == 0)
147 img_okay = ImageXYZ::ReadXYZ(data, bytes, transparent, w, h, pixels);
148 else if (bytes > 2 && strncmp((char*) data, "BM", 2) == 0)
149 img_okay = ImageBMP::ReadBMP(data, bytes, transparent, w, h, pixels);
150 else if (bytes > 4 && strncmp((char*)(data + 1), "PNG", 3) == 0)
151 img_okay = ImagePNG::ReadPNG((const void*) data, transparent, w, h, pixels);
152 else
153 Output::Warning("Unsupported image (Magic: {:02X})", bytes >= 4 ? *reinterpret_cast<const uint32_t*>(data) : 0);
154
155 if (!img_okay) {
156 free(pixels);
157 pixels = nullptr;
158 return;
159 }
160
161 Init(w, h, nullptr);
162
163 ConvertImage(w, h, pixels, transparent);
164
165 CheckPixels(flags);
166 }
167
Bitmap(Bitmap const & source,Rect const & src_rect,bool transparent)168 Bitmap::Bitmap(Bitmap const& source, Rect const& src_rect, bool transparent) {
169 format = (transparent ? pixel_format : opaque_pixel_format);
170 pixman_format = find_format(format);
171
172 Init(src_rect.width, src_rect.height, (void *) NULL);
173
174 Blit(0, 0, source, src_rect, Opacity::Opaque());
175 }
176
WritePNG(Filesystem_Stream::OutputStream & os) const177 bool Bitmap::WritePNG(Filesystem_Stream::OutputStream& os) const {
178 size_t const width = GetWidth(), height = GetHeight();
179 size_t const stride = width * 4;
180
181 std::vector<uint32_t> data(width * height);
182
183 auto dst = PixmanImagePtr{pixman_image_create_bits(PIXMAN_b8g8r8, width, height, &data.front(), stride)};
184 pixman_image_composite32(PIXMAN_OP_SRC, bitmap.get(), NULL, dst.get(),
185 0, 0, 0, 0, 0, 0, width, height);
186
187 return ImagePNG::WritePNG(os, width, height, &data.front());
188 }
189
GetSize() const190 size_t Bitmap::GetSize() const {
191 if (!bitmap) {
192 return 0;
193 }
194
195 return pitch() * height();
196 }
197
ComputeImageOpacity() const198 ImageOpacity Bitmap::ComputeImageOpacity() const {
199 bool all_opaque = true;
200 bool all_transp = true;
201
202 auto* p = reinterpret_cast<const uint32_t*>(pixels());
203 const auto mask = pixel_format.rgba_to_uint32_t(0, 0, 0, 0xFF);
204
205 int n = GetSize() / sizeof(uint32_t);
206 for (int i = 0; i < n; ++i ) {
207 auto px = p[i] & mask;
208 all_opaque &= (px == mask);
209 all_transp &= (px == 0);
210 }
211
212 return
213 all_transp ? ImageOpacity::Transparent :
214 all_opaque ? ImageOpacity::Opaque :
215 ImageOpacity::Partial;
216 }
217
ComputeImageOpacity(Rect rect) const218 ImageOpacity Bitmap::ComputeImageOpacity(Rect rect) const {
219 bool all_opaque = true;
220 bool all_transp = true;
221
222 const auto full_rect = GetRect();
223 rect = full_rect.GetSubRect(rect);
224
225 auto* p = reinterpret_cast<const uint32_t*>(pixels());
226 const int stride = pitch() / sizeof(uint32_t);
227 const auto mask = pixel_format.rgba_to_uint32_t(0, 0, 0, 0xFF);
228
229 int xend = (rect.x + rect.width);
230 int yend = (rect.y + rect.height);
231 for (int y = rect.y * stride; y < yend * stride; y += stride) {
232 for (int x = rect.x; x < xend; ++x) {
233 auto px = p[x + y] & mask;
234 all_transp &= (px == 0);
235 all_opaque &= (px == mask);
236 }
237 }
238
239 return
240 all_transp ? ImageOpacity::Transparent :
241 all_opaque ? ImageOpacity::Opaque :
242 ImageOpacity::Partial;
243 }
244
CheckPixels(uint32_t flags)245 void Bitmap::CheckPixels(uint32_t flags) {
246 if (flags & Flag_System) {
247 DynamicFormat format(32,8,24,8,16,8,8,8,0,PF::Alpha);
248 uint32_t pixel;
249 Bitmap bmp(reinterpret_cast<void*>(&pixel), 1, 1, 4, format);
250 pixman_image_composite32(PIXMAN_OP_SRC, bitmap.get(), (pixman_image_t*) NULL, bmp.bitmap.get(),
251 0, 32, 0, 0, 0, 0, 1, 1);
252 bg_color = Color((int)(pixel>>24)&0xFF, (int)(pixel>>16)&0xFF, (int)(pixel>>8)&0xFF, (int)pixel&0xFF);
253 pixman_image_composite32(PIXMAN_OP_SRC, bitmap.get(), (pixman_image_t*) NULL, bmp.bitmap.get(),
254 16, 32, 0, 0, 0, 0, 1, 1);
255 sh_color = Color((int)(pixel>>24)&0xFF, (int)(pixel>>16)&0xFF, (int)(pixel>>8)&0xFF, (int)pixel&0xFF);
256 }
257
258 if (flags & Flag_Chipset) {
259 const int h = height() / TILE_SIZE;
260 const int w = width() / TILE_SIZE;
261 tile_opacity = TileOpacity(w, h);
262
263 for (int ty = 0; ty < h; ++ty) {
264 for (int tx = 0; tx < w; ++tx) {
265 Rect rect(tx * TILE_SIZE, ty * TILE_SIZE, TILE_SIZE, TILE_SIZE);
266 auto op = ComputeImageOpacity(rect);
267 tile_opacity.Set(tx, ty, op);
268 }
269 }
270 }
271
272 if (flags & Flag_ReadOnly) {
273 read_only = true;
274
275 image_opacity = ComputeImageOpacity();
276 }
277 }
278
HueChangeBlit(int x,int y,Bitmap const & src,Rect const & src_rect_,double hue_)279 void Bitmap::HueChangeBlit(int x, int y, Bitmap const& src, Rect const& src_rect_, double hue_) {
280 Rect dst_rect(x, y, 0, 0), src_rect = src_rect_;
281
282 if (!Rect::AdjustRectangles(src_rect, dst_rect, src.GetRect()))
283 return;
284 if (!Rect::AdjustRectangles(dst_rect, src_rect, GetRect()))
285 return;
286
287 int hue = (int) (hue_ / 60.0 * 0x100);
288 if (hue < 0)
289 hue += ((-hue + 0x5FF) / 0x600) * 0x600;
290 else if (hue > 0x600)
291 hue -= (hue / 0x600) * 0x600;
292
293 DynamicFormat format(32,8,24,8,16,8,8,8,0,PF::Alpha);
294 std::vector<uint32_t> pixels;
295 pixels.resize(src_rect.width * src_rect.height);
296 Bitmap bmp(reinterpret_cast<void*>(&pixels.front()), src_rect.width, src_rect.height, src_rect.width * 4, format);
297 bmp.Blit(0, 0, src, src_rect, Opacity::Opaque());
298
299 for (std::vector<uint32_t>::iterator p = pixels.begin(); p != pixels.end(); ++p) {
300 uint32_t pixel = *p;
301 uint8_t r = (pixel>>24) & 0xFF;
302 uint8_t g = (pixel>>16) & 0xFF;
303 uint8_t b = (pixel>> 8) & 0xFF;
304 uint8_t a = pixel & 0xFF;
305 if (a > 0)
306 RGB_adjust_HSL(r, g, b, hue);
307 *p = ((uint32_t) r << 24) | ((uint32_t) g << 16) | ((uint32_t) b << 8) | (uint32_t) a;
308 }
309
310 Blit(dst_rect.x, dst_rect.y, bmp, bmp.GetRect(), Opacity::Opaque());
311 }
312
TextDraw(Rect const & rect,int color,StringView text,Text::Alignment align)313 void Bitmap::TextDraw(Rect const& rect, int color, StringView text, Text::Alignment align) {
314 FontRef font = Font::Default();
315 Rect text_rect = font->GetSize(text);
316 int dx = text_rect.width - rect.width;
317
318 switch (align) {
319 case Text::AlignLeft:
320 TextDraw(rect.x, rect.y, color, text);
321 break;
322 case Text::AlignCenter:
323 TextDraw(rect.x + dx / 2, rect.y, color, text);
324 break;
325 case Text::AlignRight:
326 TextDraw(rect.x + dx, rect.y, color, text);
327 break;
328 default: assert(false);
329 }
330 }
331
TextDraw(int x,int y,int color,StringView text,Text::Alignment align)332 void Bitmap::TextDraw(int x, int y, int color, StringView text, Text::Alignment align) {
333 auto font = Font::Default();
334 auto system = Cache::SystemOrBlack();
335 Text::Draw(*this, x, y, *font, *system, color, text, align);
336 }
337
TextDraw(Rect const & rect,Color color,StringView text,Text::Alignment align)338 void Bitmap::TextDraw(Rect const& rect, Color color, StringView text, Text::Alignment align) {
339 FontRef font = Font::Default();
340 Rect text_rect = font->GetSize(text);
341 int dx = text_rect.width - rect.width;
342
343 switch (align) {
344 case Text::AlignLeft:
345 TextDraw(rect.x, rect.y, color, text);
346 break;
347 case Text::AlignCenter:
348 TextDraw(rect.x + dx / 2, rect.y, color, text);
349 break;
350 case Text::AlignRight:
351 TextDraw(rect.x + dx, rect.y, color, text);
352 break;
353 default: assert(false);
354 }
355 }
356
TextDraw(int x,int y,Color color,StringView text)357 void Bitmap::TextDraw(int x, int y, Color color, StringView text) {
358 auto font = Font::Default();
359 Text::Draw(*this, x, y, *font, color, text);
360 }
361
TransformRectangle(const Transform & xform,const Rect & rect)362 Rect Bitmap::TransformRectangle(const Transform& xform, const Rect& rect) {
363 pixman_box16 bounds = {
364 static_cast<int16_t>(rect.x),
365 static_cast<int16_t>(rect.y),
366 static_cast<int16_t>(rect.x + rect.width),
367 static_cast<int16_t>(rect.y + rect.height)
368 };
369
370 pixman_transform_bounds(&xform.matrix, &bounds);
371 return Rect(bounds.x1, bounds.y1, bounds.x2 - bounds.x1, bounds.y2 - bounds.y1);
372 }
373
374 static constexpr std::array<std::pair<int,pixman_format_code_t>, 27> formats_map = {{
375 { DynamicFormat(32,8,24,8,16,8,8,8,0,PF::Alpha).code_alpha(), PIXMAN_r8g8b8a8 },
376 { DynamicFormat(32,8,24,8,16,8,8,8,0,PF::NoAlpha).code_alpha(), PIXMAN_r8g8b8x8 },
377
378 { DynamicFormat(32,8,16,8,8,8,0,8,24,PF::Alpha).code_alpha(), PIXMAN_a8r8g8b8 },
379 { DynamicFormat(32,8,16,8,8,8,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x8r8g8b8 },
380 { DynamicFormat(32,8,0,8,8,8,16,8,24,PF::Alpha).code_alpha(), PIXMAN_a8b8g8r8 },
381 { DynamicFormat(32,8,0,8,8,8,16,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x8b8g8r8 },
382 { DynamicFormat(32,8,8,8,16,8,24,8,0,PF::Alpha).code_alpha(), PIXMAN_b8g8r8a8 },
383 { DynamicFormat(32,8,8,8,16,8,24,0,0,PF::NoAlpha).code_alpha(), PIXMAN_b8g8r8x8 },
384
385 { DynamicFormat(32,6,12,6,6,6,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x14r6g6b6 },
386 { DynamicFormat(32,10,20,10,10,10,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x2r10g10b10 },
387 { DynamicFormat(32,10,20,10,10,10,0,2,30,PF::Alpha).code_alpha(), PIXMAN_a2r10g10b10 },
388 { DynamicFormat(32,10,0,10,10,10,20,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x2b10g10r10 },
389 { DynamicFormat(32,10,0,10,10,10,20,2,30,PF::Alpha).code_alpha(), PIXMAN_a2b10g10r10 },
390
391 { DynamicFormat(24,8,16,8,8,8,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_r8g8b8 },
392 { DynamicFormat(24,8,0,8,8,8,16,0,0,PF::NoAlpha).code_alpha(), PIXMAN_b8g8r8 },
393
394 { DynamicFormat(16,5,11,6,5,5,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_r5g6b5 },
395 { DynamicFormat(16,5,0,6,5,5,11,0,0,PF::NoAlpha).code_alpha(), PIXMAN_b5g6r5 },
396 { DynamicFormat(16,5,10,5,5,5,0,1,15,PF::Alpha).code_alpha(), PIXMAN_a1r5g5b5 },
397 { DynamicFormat(16,5,10,5,5,5,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x1r5g5b5 },
398 { DynamicFormat(16,5,0,5,5,5,10,1,15,PF::Alpha).code_alpha(), PIXMAN_a1b5g5r5 },
399 { DynamicFormat(16,5,0,5,5,5,10,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x1b5g5r5 },
400 { DynamicFormat(16,4,8,4,4,4,0,4,12,PF::Alpha).code_alpha(), PIXMAN_a4r4g4b4 },
401 { DynamicFormat(16,4,8,4,4,4,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x4r4g4b4 },
402 { DynamicFormat(16,4,0,4,4,4,8,4,12,PF::Alpha).code_alpha(), PIXMAN_a4b4g4r4 },
403 { DynamicFormat(16,4,0,4,4,4,8,0,0,PF::NoAlpha).code_alpha(), PIXMAN_x4b4g4r4 },
404 { DynamicFormat(8,8,0,8,0,8,0,8,0,PF::Alpha).code_alpha(), PIXMAN_g8 },
405 { DynamicFormat(8,8,0,8,0,8,0,0,0,PF::NoAlpha).code_alpha(), PIXMAN_g8 }
406 }};
407
find_format(const DynamicFormat & format)408 pixman_format_code_t Bitmap::find_format(const DynamicFormat& format) {
409 auto dcode = format.code_alpha();
410 auto iter = std::find_if(formats_map.begin(), formats_map.end(), [dcode](const auto& p) { return p.first == dcode; });
411 if (iter == formats_map.end()) {
412 // To fix add a pair to initialize_formats that maps the outputted
413 // DynamicFormat to a pixman format
414 Output::Error("{}\nDynamicFormat({}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
415 "Couldn't find Pixman format for",
416 format.bits,
417 format.r.bits, format.r.shift,
418 format.g.bits, format.g.shift,
419 format.b.bits, format.b.shift,
420 format.a.bits, format.a.shift,
421 format.alpha_type == PF::Alpha ? "PF::Alpha" : "PF::NoAlpha");
422 }
423 return iter->second;
424 }
425
426 DynamicFormat Bitmap::pixel_format;
427 DynamicFormat Bitmap::opaque_pixel_format;
428 DynamicFormat Bitmap::image_format;
429 DynamicFormat Bitmap::opaque_image_format;
430
SetFormat(const DynamicFormat & format)431 void Bitmap::SetFormat(const DynamicFormat& format) {
432 pixel_format = format;
433 opaque_pixel_format = format;
434 opaque_pixel_format.alpha_type = PF::NoAlpha;
435 image_format = format_R8G8B8A8_a().format();
436 opaque_image_format = format_R8G8B8A8_n().format();
437 }
438
ChooseFormat(const DynamicFormat & format)439 DynamicFormat Bitmap::ChooseFormat(const DynamicFormat& format) {
440 uint32_t amask;
441 amask = (format.a.mask == 0)
442 ? ((~0U >> (32 - format.bits)) ^ (format.r.mask | format.g.mask | format.b.mask))
443 : format.a.mask;
444 if (amask != 0)
445 return DynamicFormat(format.bits,
446 format.r.mask, format.g.mask, format.b.mask,
447 amask, PF::Alpha);
448 switch (format.bits) {
449 case 16:
450 return (format.r.shift > format.b.shift)
451 ? DynamicFormat(16,5,10,5,5,5,0,1,15,PF::Alpha)
452 : DynamicFormat(16,5,0,5,5,5,10,1,15,PF::Alpha);
453 case 24:
454 return (format.r.shift > format.b.shift)
455 ? DynamicFormat(32,8,16,8,8,8,0,8,24,PF::Alpha)
456 : DynamicFormat(32,8,0,8,8,8,16,8,24,PF::Alpha);
457 default:
458 return format_B8G8R8A8_a().format();
459 }
460 }
461
destroy_func(pixman_image_t *,void * data)462 static void destroy_func(pixman_image_t * /* image */, void *data) {
463 free(data);
464 }
465
466 static pixman_indexed_t palette;
467 static bool palette_initialized = false;
468
initialize_palette()469 static void initialize_palette() {
470 if (palette_initialized)
471 return;
472 palette.color = false;
473 palette.rgba[0] = 0U;
474 for (int i = 1; i < PIXMAN_MAX_INDEXED; i++)
475 palette.rgba[i] = ~0U;
476 palette_initialized = true;
477 }
478
Init(int width,int height,void * data,int pitch,bool destroy)479 void Bitmap::Init(int width, int height, void* data, int pitch, bool destroy) {
480 if (!pitch)
481 pitch = width * format.bytes;
482
483 bitmap.reset(pixman_image_create_bits(pixman_format, width, height, (uint32_t*) data, pitch));
484
485 if (bitmap == NULL) {
486 Output::Error("Couldn't create {}x{} image.", width, height);
487 }
488
489 if (format.bits == 8) {
490 initialize_palette();
491 pixman_image_set_indexed(bitmap.get(), &palette);
492 }
493
494 if (data != NULL && destroy)
495 pixman_image_set_destroy_function(bitmap.get(), destroy_func, data);
496 }
497
ConvertImage(int & width,int & height,void * & pixels,bool transparent)498 void Bitmap::ConvertImage(int& width, int& height, void*& pixels, bool transparent) {
499 const DynamicFormat& img_format = transparent ? image_format : opaque_image_format;
500
501 // premultiply alpha
502 for (int y = 0; y < height; y++) {
503 uint8_t* dst = (uint8_t*) pixels + y * width * 4;
504 for (int x = 0; x < width; x++) {
505 uint8_t &r = *dst++;
506 uint8_t &g = *dst++;
507 uint8_t &b = *dst++;
508 uint8_t &a = *dst++;
509 MultiplyAlpha(r, g, b, a);
510 }
511 }
512
513 Bitmap src(pixels, width, height, 0, img_format);
514 Clear();
515 Blit(0, 0, src, src.GetRect(), Opacity::Opaque());
516 free(pixels);
517 }
518
pixels()519 void* Bitmap::pixels() {
520 if (!bitmap) {
521 return nullptr;
522 }
523
524 return (void*) pixman_image_get_data(bitmap.get());
525 }
pixels() const526 void const* Bitmap::pixels() const {
527 return (void const*) pixman_image_get_data(bitmap.get());
528 }
529
bpp() const530 int Bitmap::bpp() const {
531 return (pixman_image_get_depth(bitmap.get()) + 7) / 8;
532 }
533
width() const534 int Bitmap::width() const {
535 return pixman_image_get_width(bitmap.get());
536 }
537
height() const538 int Bitmap::height() const {
539 return pixman_image_get_height(bitmap.get());
540 }
541
pitch() const542 int Bitmap::pitch() const {
543 return pixman_image_get_stride(bitmap.get());
544 }
545
546 namespace {
CreateMask(Opacity const & opacity,Rect const & src_rect,Transform const * pxform=nullptr)547 PixmanImagePtr CreateMask(Opacity const& opacity, Rect const& src_rect, Transform const* pxform = nullptr) {
548 if (opacity.IsOpaque()) {
549 return nullptr;
550 }
551
552 if (!opacity.IsSplit()) {
553 pixman_color_t tcolor = {0, 0, 0, static_cast<uint16_t>(opacity.Value() << 8)};
554 return PixmanImagePtr{ pixman_image_create_solid_fill(&tcolor) };
555 }
556
557 auto mask = PixmanImagePtr{pixman_image_create_bits(PIXMAN_a8, 1, 2, (uint32_t*) NULL, 4)};
558 uint32_t* pixels = pixman_image_get_data(mask.get());
559 *reinterpret_cast<uint8_t*>(&pixels[0]) = (opacity.top & 0xFF);
560 *reinterpret_cast<uint8_t*>(&pixels[1]) = (opacity.bottom & 0xFF);
561
562 Transform xform = Transform::Scale(1.0 / src_rect.width, 1.0 / src_rect.height);
563 xform *= Transform::Translation(0, opacity.split);
564
565 if (pxform)
566 xform *= *pxform;
567
568 pixman_image_set_transform(mask.get(), &xform.matrix);
569
570 return mask;
571 }
572 } // anonymous namespace
573
Blit(int x,int y,Bitmap const & src,Rect const & src_rect,Opacity const & opacity,Bitmap::BlendMode blend_mode)574 void Bitmap::Blit(int x, int y, Bitmap const& src, Rect const& src_rect, Opacity const& opacity, Bitmap::BlendMode blend_mode) {
575 if (opacity.IsTransparent()) {
576 return;
577 }
578
579 auto mask = CreateMask(opacity, src_rect);
580
581 pixman_image_composite32(src.GetOperator(mask.get(), blend_mode),
582 src.bitmap.get(),
583 mask.get(), bitmap.get(),
584 src_rect.x, src_rect.y,
585 0, 0,
586 x, y,
587 src_rect.width, src_rect.height);
588 }
589
BlitFast(int x,int y,Bitmap const & src,Rect const & src_rect,Opacity const & opacity)590 void Bitmap::BlitFast(int x, int y, Bitmap const & src, Rect const & src_rect, Opacity const & opacity) {
591 if (opacity.IsTransparent()) {
592 return;
593 }
594
595 pixman_image_composite32(PIXMAN_OP_SRC,
596 src.bitmap.get(),
597 nullptr, bitmap.get(),
598 src_rect.x, src_rect.y,
599 0, 0,
600 x, y,
601 src_rect.width, src_rect.height);
602 }
603
GetSubimage(Bitmap const & src,const Rect & src_rect)604 PixmanImagePtr Bitmap::GetSubimage(Bitmap const& src, const Rect& src_rect) {
605 uint8_t* pixels = (uint8_t*) src.pixels() + src_rect.x * src.bpp() + src_rect.y * src.pitch();
606 return PixmanImagePtr{ pixman_image_create_bits(src.pixman_format, src_rect.width, src_rect.height,
607 (uint32_t*) pixels, src.pitch()) };
608 }
609
TiledBlit(Rect const & src_rect,Bitmap const & src,Rect const & dst_rect,Opacity const & opacity,Bitmap::BlendMode blend_mode)610 void Bitmap::TiledBlit(Rect const& src_rect, Bitmap const& src, Rect const& dst_rect, Opacity const& opacity, Bitmap::BlendMode blend_mode) {
611 TiledBlit(0, 0, src_rect, src, dst_rect, opacity, blend_mode);
612 }
613
TiledBlit(int ox,int oy,Rect const & src_rect,Bitmap const & src,Rect const & dst_rect,Opacity const & opacity,Bitmap::BlendMode blend_mode)614 void Bitmap::TiledBlit(int ox, int oy, Rect const& src_rect, Bitmap const& src, Rect const& dst_rect, Opacity const& opacity, Bitmap::BlendMode blend_mode) {
615 if (opacity.IsTransparent()) {
616 return;
617 }
618
619 if (ox >= src_rect.width) ox %= src_rect.width;
620 if (oy >= src_rect.height) oy %= src_rect.height;
621 if (ox < 0) ox += src_rect.width * ((-ox + src_rect.width - 1) / src_rect.width);
622 if (oy < 0) oy += src_rect.height * ((-oy + src_rect.height - 1) / src_rect.height);
623
624 auto src_bm = GetSubimage(src, src_rect);
625
626 pixman_image_set_repeat(src_bm.get(), PIXMAN_REPEAT_NORMAL);
627
628 auto mask = CreateMask(opacity, src_rect);
629
630 pixman_image_composite32(src.GetOperator(mask.get(), blend_mode),
631 src_bm.get(), mask.get(), bitmap.get(),
632 ox, oy,
633 0, 0,
634 dst_rect.x, dst_rect.y,
635 dst_rect.width, dst_rect.height);
636 }
637
StretchBlit(Bitmap const & src,Rect const & src_rect,Opacity const & opacity,Bitmap::BlendMode blend_mode)638 void Bitmap::StretchBlit(Bitmap const& src, Rect const& src_rect, Opacity const& opacity, Bitmap::BlendMode blend_mode) {
639 StretchBlit(GetRect(), src, src_rect, opacity, blend_mode);
640 }
641
StretchBlit(Rect const & dst_rect,Bitmap const & src,Rect const & src_rect,Opacity const & opacity,Bitmap::BlendMode blend_mode)642 void Bitmap::StretchBlit(Rect const& dst_rect, Bitmap const& src, Rect const& src_rect, Opacity const& opacity, Bitmap::BlendMode blend_mode) {
643 if (opacity.IsTransparent()) {
644 return;
645 }
646
647 double zoom_x = (double)src_rect.width / dst_rect.width;
648 double zoom_y = (double)src_rect.height / dst_rect.height;
649
650 Transform xform = Transform::Scale(zoom_x, zoom_y);
651
652 pixman_image_set_transform(src.bitmap.get(), &xform.matrix);
653
654 auto mask = CreateMask(opacity, src_rect, &xform);
655
656 pixman_image_composite32(src.GetOperator(mask.get(), blend_mode),
657 src.bitmap.get(), mask.get(), bitmap.get(),
658 src_rect.x / zoom_x, src_rect.y / zoom_y,
659 0, 0,
660 dst_rect.x, dst_rect.y,
661 dst_rect.width, dst_rect.height);
662
663 pixman_image_set_transform(src.bitmap.get(), nullptr);
664 }
665
WaverBlit(int x,int y,double zoom_x,double zoom_y,Bitmap const & src,Rect const & src_rect,int depth,double phase,Opacity const & opacity,Bitmap::BlendMode blend_mode)666 void Bitmap::WaverBlit(int x, int y, double zoom_x, double zoom_y, Bitmap const& src, Rect const& src_rect, int depth, double phase, Opacity const& opacity, Bitmap::BlendMode blend_mode) {
667 if (opacity.IsTransparent()) {
668 return;
669 }
670
671 Transform xform = Transform::Scale(1.0 / zoom_x, 1.0 / zoom_y);
672
673 pixman_image_set_transform(src.bitmap.get(), &xform.matrix);
674
675 auto mask = CreateMask(opacity, src_rect, &xform);
676
677 int height = static_cast<int>(std::floor(src_rect.height * zoom_y));
678 int width = static_cast<int>(std::floor(src_rect.width * zoom_x));
679 const auto xoff = src_rect.x * zoom_x;
680 const auto yoff = src_rect.y * zoom_y;
681 const auto yclip = y < 0 ? -y : 0;
682 const auto yend = std::min(height, this->height() - y);
683 for (int i = yclip; i < yend; i++) {
684 int dy = y + i;
685 // RPG_RT starts the effect from the top of the screen even if the image is clipped. The result
686 // is that moving images which cross the top of the screen can appear to go too fast or too slow
687 // in RPT_RT. The (i - yclip) is RPG_RT compatible behavior. Just (i) would be more correct.
688 const double sy = (i - yclip) * (2 * M_PI) / (32.0 * zoom_y);
689 const int offset = 2 * zoom_x * depth * std::sin(phase + sy);
690
691 pixman_image_composite32(src.GetOperator(mask.get(), blend_mode),
692 src.bitmap.get(), mask.get(), bitmap.get(),
693 xoff, yoff + i,
694 0, i,
695 x + offset, dy,
696 width, 1);
697 }
698
699 pixman_image_set_transform(src.bitmap.get(), nullptr);
700 }
701
PixmanColor(const Color & color)702 static pixman_color_t PixmanColor(const Color &color) {
703 pixman_color_t pcolor;
704 pcolor.red = color.red * color.alpha;
705 pcolor.green = color.green * color.alpha;
706 pcolor.blue = color.blue * color.alpha;
707 pcolor.alpha = color.alpha << 8;
708 return pcolor;
709 }
710
Fill(const Color & color)711 void Bitmap::Fill(const Color &color) {
712 pixman_color_t pcolor = PixmanColor(color);
713
714 pixman_box32_t box = { 0, 0, width(), height() };
715
716 pixman_image_fill_boxes(PIXMAN_OP_SRC, bitmap.get(), &pcolor, 1, &box);
717 }
718
FillRect(Rect const & dst_rect,const Color & color)719 void Bitmap::FillRect(Rect const& dst_rect, const Color &color) {
720 pixman_color_t pcolor = PixmanColor(color);
721
722 auto timage = PixmanImagePtr{pixman_image_create_solid_fill(&pcolor)};
723
724 pixman_image_composite32(PIXMAN_OP_OVER,
725 timage.get(), nullptr, bitmap.get(),
726 0, 0,
727 0, 0,
728 dst_rect.x, dst_rect.y,
729 dst_rect.width, dst_rect.height);
730 }
731
Clear()732 void Bitmap::Clear() {
733 if (!pixels()) {
734 // Happens when height or width of bitmap are 0
735 return;
736 }
737
738 memset(pixels(), '\0', height() * pitch());
739 }
740
ClearRect(Rect const & dst_rect)741 void Bitmap::ClearRect(Rect const& dst_rect) {
742 pixman_color_t pcolor = {};
743 pixman_box32_t box = {
744 dst_rect.x,
745 dst_rect.y,
746 dst_rect.x + dst_rect.width,
747 dst_rect.y + dst_rect.height
748 };
749
750 pixman_image_fill_boxes(PIXMAN_OP_CLEAR, bitmap.get(), &pcolor, 1, &box);
751 }
752
753 // Hard light lookup table mapping source color to destination color
754 // FIXME: Replace this with std::array<std::array<uint8_t,256>,256> when we have C++17
755 struct HardLightTable {
756 uint8_t table[256][256] = {};
757 };
758
make_hard_light_lookup()759 static constexpr HardLightTable make_hard_light_lookup() {
760 HardLightTable hl;
761 for (int i = 0; i < 256; ++i) {
762 for (int j = 0; j < 256; ++j) {
763 int res = 0;
764 if (i <= 128)
765 res = (2 * i * j) / 255;
766 else
767 res = 255 - 2 * (255 - i) * (255 - j) / 255;
768 hl.table[i][j] = res > 255 ? 255 : res < 0 ? 0 : res;
769 }
770 }
771 return hl;
772 }
773
774 constexpr auto hard_light = make_hard_light_lookup();
775
776 // Saturation Tone Inline: Changes a pixel saturation
saturation_tone(uint32_t & src_pixel,int saturation,int rs,int gs,int bs,int as)777 static inline void saturation_tone(uint32_t &src_pixel, int saturation, int rs, int gs, int bs, int as) {
778 // Algorithm from OpenPDN (MIT license)
779 // Transformation in Y'CbCr color space
780 uint8_t r = (src_pixel >> rs) & 0xFF;
781 uint8_t g = (src_pixel >> gs) & 0xFF;
782 uint8_t b = (src_pixel >> bs) & 0xFF;
783 uint8_t a = (src_pixel >> as) & 0xFF;
784
785 // Y' = 0.299 R' + 0.587 G' + 0.114 B'
786 uint8_t lum = (7471 * b + 38470 * g + 19595 * r) >> 16;
787
788 // Scale Cb/Cr by scale factor "sat"
789 int red = ((lum * 1024 + (r - lum) * saturation) >> 10);
790 red = red > 255 ? 255 : red < 0 ? 0 : red;
791 int green = ((lum * 1024 + (g - lum) * saturation) >> 10);
792 green = green > 255 ? 255 : green < 0 ? 0 : green;
793 int blue = ((lum * 1024 + (b - lum) * saturation) >> 10);
794 blue = blue > 255 ? 255 : blue < 0 ? 0 : blue;
795
796 src_pixel = ((uint32_t)red << rs) | ((uint32_t)green << gs) | ((uint32_t)blue << bs) | ((uint32_t)a << as);
797 }
798
799 // Color Tone Inline: Changes color of a pixel by hard light table
color_tone(uint32_t & src_pixel,Tone tone,int rs,int gs,int bs,int as)800 static inline void color_tone(uint32_t &src_pixel, Tone tone, int rs, int gs, int bs, int as) {
801 src_pixel = ((uint32_t)hard_light.table[tone.red][(src_pixel >> rs) & 0xFF] << rs)
802 | ((uint32_t)hard_light.table[tone.green][(src_pixel >> gs) & 0xFF] << gs)
803 | ((uint32_t)hard_light.table[tone.blue][(src_pixel >> bs) & 0xFF] << bs)
804 | ((uint32_t)((src_pixel >> as) & 0xFF) << as);
805 }
806
ToneBlit(int x,int y,Bitmap const & src,Rect const & src_rect,const Tone & tone,Opacity const & opacity,bool check_alpha)807 void Bitmap::ToneBlit(int x, int y, Bitmap const& src, Rect const& src_rect, const Tone &tone, Opacity const& opacity, bool check_alpha) {
808 if (opacity.IsTransparent()) {
809 return;
810 }
811
812 if (tone == Tone(128,128,128,128)) {
813 if (&src != this) {
814 Blit(x, y, src, src_rect, opacity);
815 }
816 return;
817 }
818
819 // Only needed here, other codepaths are sanity checked by pixman
820 if (x < 0 || y < 0 || x >= width() || y >= height()) {
821 return;
822 }
823
824 if (&src != this)
825 pixman_image_composite32(src.GetOperator(),
826 src.bitmap.get(), nullptr, bitmap.get(),
827 src_rect.x, src_rect.y,
828 0, 0,
829 x, y,
830 src_rect.width, src_rect.height);
831
832 int as = pixel_format.a.shift;
833 int rs = pixel_format.r.shift;
834 int gs = pixel_format.g.shift;
835 int bs = pixel_format.b.shift;
836 int next_row = pitch() / sizeof(uint32_t);
837 uint32_t* pixels = (uint32_t*)this->pixels();
838 pixels = pixels + (y - 1) * next_row + x;
839
840 uint16_t limit_height = std::min<uint16_t>(src_rect.height, height());
841 uint16_t limit_width = std::min<uint16_t>(src_rect.width, width());
842
843 // If Saturation + Color:
844 if (tone.gray != 128 && (tone.red != 128 || tone.green != 128 || tone.blue != 128)) {
845 int sat = tone.gray > 128 ? 1024 + (tone.gray - 128) * 16 : tone.gray * 8;
846
847 if (&src != this || check_alpha) {
848 for (uint16_t i = 0; i < limit_height; ++i) {
849 pixels += next_row;
850 for (uint16_t j = 0; j < limit_width; ++j) {
851 if ((uint8_t)((pixels[j] >> as) & 0xFF) == 0)
852 continue;
853
854 saturation_tone(pixels[j], sat, rs, gs, bs, as);
855 color_tone(pixels[j], tone, rs, gs, bs, as);
856 }
857 }
858 }
859 else {
860 for (uint16_t i = 0; i < limit_height; ++i) {
861 pixels += next_row;
862 for (uint16_t j = 0; j < limit_width; ++j) {
863 saturation_tone(pixels[j], sat, rs, gs, bs, as);
864 color_tone(pixels[j], tone, rs, gs, bs, as);
865 }
866 }
867 }
868 }
869
870 // If Only Saturation:
871 else if (tone.gray != 128) {
872 int sat = tone.gray > 128 ? 1024 + (tone.gray - 128) * 16 : tone.gray * 8;
873
874 if (&src != this || check_alpha) {
875 for (uint16_t i = 0; i < limit_height; ++i) {
876 pixels += next_row;
877 for (uint16_t j = 0; j < limit_width; ++j) {
878 if ((uint8_t)((pixels[j] >> as) & 0xFF) == 0)
879 continue;
880
881 saturation_tone(pixels[j], sat, rs, gs, bs, as);
882 }
883 }
884 }
885 else {
886 for (uint16_t i = 0; i < limit_height; ++i) {
887 pixels += next_row;
888 for (uint16_t j = 0; j < limit_width; ++j) {
889 saturation_tone(pixels[j], sat, rs, gs, bs, as);
890 }
891 }
892 }
893 }
894
895 // If Only Color:
896 else if (tone.red != 128 || tone.green != 128 || tone.blue != 128) {
897 if (&src != this || check_alpha) {
898 for (uint16_t i = 0; i < limit_height; ++i) {
899 pixels += next_row;
900 for (uint16_t j = 0; j < limit_width; ++j) {
901 if ((uint8_t)((pixels[j] >> as) & 0xFF) == 0)
902 continue;
903
904 color_tone(pixels[j], tone, rs, gs, bs, as);
905 }
906 }
907 }
908 else {
909 for (uint16_t i = 0; i < limit_height; ++i) {
910 pixels += next_row;
911 for (uint16_t j = 0; j < limit_width; ++j) {
912 color_tone(pixels[j], tone, rs, gs, bs, as);
913 }
914 }
915 }
916
917 }
918
919 }
920
BlendBlit(int x,int y,Bitmap const & src,Rect const & src_rect,const Color & color,Opacity const & opacity)921 void Bitmap::BlendBlit(int x, int y, Bitmap const& src, Rect const& src_rect, const Color& color, Opacity const& opacity) {
922 if (opacity.IsTransparent()) {
923 return;
924 }
925
926 if (color.alpha == 0) {
927 if (&src != this)
928 Blit(x, y, src, src_rect, opacity);
929 return;
930 }
931
932 if (&src != this)
933 pixman_image_composite32(src.GetOperator(),
934 src.bitmap.get(), nullptr, bitmap.get(),
935 src_rect.x, src_rect.y,
936 0, 0,
937 x, y,
938 src_rect.width, src_rect.height);
939
940 pixman_color_t tcolor = PixmanColor(color);
941 auto timage = PixmanImagePtr{ pixman_image_create_solid_fill(&tcolor) };
942
943 pixman_image_composite32(PIXMAN_OP_OVER,
944 timage.get(), src.bitmap.get(), bitmap.get(),
945 0, 0,
946 src_rect.x, src_rect.y,
947 x, y,
948 src_rect.width, src_rect.height);
949 }
950
FlipBlit(int x,int y,Bitmap const & src,Rect const & src_rect,bool horizontal,bool vertical,Opacity const & opacity,Bitmap::BlendMode blend_mode)951 void Bitmap::FlipBlit(int x, int y, Bitmap const& src, Rect const& src_rect, bool horizontal, bool vertical, Opacity const& opacity, Bitmap::BlendMode blend_mode) {
952 if (opacity.IsTransparent()) {
953 return;
954 }
955
956 bool has_xform = (horizontal || vertical);
957 const auto img_w = src.GetWidth();
958 const auto img_h = src.GetHeight();
959
960 auto rect = src_rect;
961 if (has_xform) {
962 Transform xform = Transform::Scale(horizontal ? -1 : 1, vertical ? -1 : 1);
963 xform *= Transform::Translation(horizontal ? -img_w : 0, vertical ? -img_h : 0);
964
965 pixman_image_set_transform(src.bitmap.get(), &xform.matrix);
966 const auto src_x = horizontal ? img_w - src_rect.x - src_rect.width : src_rect.x;
967 const auto src_y = vertical ? img_h - src_rect.y - src_rect.height : src_rect.y;
968
969 rect = Rect{ src_x, src_y, src_rect.width, src_rect.height };
970 }
971
972 Blit(x, y, src, rect, opacity, blend_mode);
973
974 if (has_xform) {
975 pixman_image_set_transform(src.bitmap.get(), nullptr);
976 }
977 }
978
Flip(bool horizontal,bool vertical)979 void Bitmap::Flip(bool horizontal, bool vertical) {
980 if (!horizontal && !vertical) {
981 return;
982 }
983 const auto w = GetWidth();
984 const auto h = GetHeight();
985 const auto p = pitch();
986
987 auto temp = PixmanImagePtr{ pixman_image_create_bits(pixman_format, w, h, nullptr, p) };
988
989 std::memcpy(pixman_image_get_data(temp.get()),
990 pixman_image_get_data(bitmap.get()),
991 p * h);
992
993 Transform xform = Transform::Scale(horizontal ? -1 : 1, vertical ? -1 : 1);
994 xform *= Transform::Translation(horizontal ? -w : 0, vertical ? -h : 0);
995
996 pixman_image_set_transform(temp.get(), &xform.matrix);
997
998 pixman_image_composite32(PIXMAN_OP_SRC,
999 temp.get(), nullptr, bitmap.get(),
1000 0, 0, 0, 0, 0, 0, w, h);
1001 }
1002
MaskedBlit(Rect const & dst_rect,Bitmap const & mask,int mx,int my,Color const & color)1003 void Bitmap::MaskedBlit(Rect const& dst_rect, Bitmap const& mask, int mx, int my, Color const& color) {
1004 pixman_color_t tcolor = {
1005 static_cast<uint16_t>(color.red << 8),
1006 static_cast<uint16_t>(color.green << 8),
1007 static_cast<uint16_t>(color.blue << 8),
1008 static_cast<uint16_t>(color.alpha << 8)};
1009
1010 auto source = PixmanImagePtr{ pixman_image_create_solid_fill(&tcolor) };
1011
1012 pixman_image_composite32(PIXMAN_OP_OVER,
1013 source.get(), mask.bitmap.get(), bitmap.get(),
1014 0, 0,
1015 mx, my,
1016 dst_rect.x, dst_rect.y,
1017 dst_rect.width, dst_rect.height);
1018 }
1019
MaskedBlit(Rect const & dst_rect,Bitmap const & mask,int mx,int my,Bitmap const & src,int sx,int sy)1020 void Bitmap::MaskedBlit(Rect const& dst_rect, Bitmap const& mask, int mx, int my, Bitmap const& src, int sx, int sy) {
1021 pixman_image_composite32(PIXMAN_OP_OVER,
1022 src.bitmap.get(), mask.bitmap.get(), bitmap.get(),
1023 sx, sy,
1024 mx, my,
1025 dst_rect.x, dst_rect.y,
1026 dst_rect.width, dst_rect.height);
1027 }
1028
Blit2x(Rect const & dst_rect,Bitmap const & src,Rect const & src_rect)1029 void Bitmap::Blit2x(Rect const& dst_rect, Bitmap const& src, Rect const& src_rect) {
1030 Transform xform = Transform::Scale(0.5, 0.5);
1031
1032 pixman_image_set_transform(src.bitmap.get(), &xform.matrix);
1033
1034 pixman_image_composite32(PIXMAN_OP_SRC,
1035 src.bitmap.get(), nullptr, bitmap.get(),
1036 src_rect.x, src_rect.y,
1037 0, 0,
1038 dst_rect.x, dst_rect.y,
1039 dst_rect.width, dst_rect.height);
1040
1041 pixman_image_set_transform(src.bitmap.get(), nullptr);
1042 }
1043
EffectsBlit(int x,int y,int ox,int oy,Bitmap const & src,Rect const & src_rect,Opacity const & opacity,double zoom_x,double zoom_y,double angle,int waver_depth,double waver_phase,Bitmap::BlendMode blend_mode)1044 void Bitmap::EffectsBlit(int x, int y, int ox, int oy,
1045 Bitmap const& src, Rect const& src_rect,
1046 Opacity const& opacity,
1047 double zoom_x, double zoom_y, double angle,
1048 int waver_depth, double waver_phase, Bitmap::BlendMode blend_mode) {
1049 if (opacity.IsTransparent()) {
1050 return;
1051 }
1052
1053 bool rotate = angle != 0.0;
1054 bool scale = zoom_x != 1.0 || zoom_y != 1.0;
1055 bool waver = waver_depth != 0;
1056
1057 if (waver) {
1058 WaverBlit(x - ox * zoom_x, y - oy * zoom_y, zoom_x, zoom_y, src, src_rect,
1059 waver_depth, waver_phase, opacity, blend_mode);
1060 }
1061 else if (rotate) {
1062 RotateZoomOpacityBlit(x, y, ox, oy, src, src_rect, angle, zoom_x, zoom_y, opacity, blend_mode);
1063 }
1064 else if (scale) {
1065 ZoomOpacityBlit(x, y, ox, oy, src, src_rect, zoom_x, zoom_y, opacity, blend_mode);
1066 }
1067 else {
1068 Blit(x - ox, y - oy, src, src_rect, opacity, blend_mode);
1069 }
1070 }
1071
RotateZoomOpacityBlit(int x,int y,int ox,int oy,Bitmap const & src,Rect const & src_rect,double angle,double zoom_x,double zoom_y,Opacity const & opacity,Bitmap::BlendMode blend_mode)1072 void Bitmap::RotateZoomOpacityBlit(int x, int y, int ox, int oy,
1073 Bitmap const& src, Rect const& src_rect,
1074 double angle, double zoom_x, double zoom_y, Opacity const& opacity, Bitmap::BlendMode blend_mode)
1075 {
1076 if (opacity.IsTransparent()) {
1077 return;
1078 }
1079
1080 auto* src_img = src.bitmap.get();
1081
1082 Transform fwd = Transform::Translation(x, y);
1083 fwd *= Transform::Rotation(angle);
1084 if (zoom_x != 1.0 || zoom_y != 1.0) {
1085 fwd *= Transform::Scale(zoom_x, zoom_y);
1086 }
1087 fwd *= Transform::Translation(-ox, -oy);
1088
1089 Rect dst_rect = TransformRectangle(fwd, Rect{0, 0, src_rect.width, src_rect.height});
1090 dst_rect.Adjust(GetRect());
1091 if (dst_rect.IsEmpty())
1092 return;
1093
1094 auto inv = fwd.Inverse();
1095
1096 PixmanImagePtr temp;
1097 if (src_rect != src.GetRect()) {
1098 temp = GetSubimage(src, src_rect);
1099 src_img = temp.get();
1100 }
1101
1102 pixman_image_set_transform(src_img, &inv.matrix);
1103
1104 auto mask = CreateMask(opacity, src_rect, &inv);
1105
1106 pixman_image_composite32(GetOperator(mask.get(), blend_mode),
1107 src_img, mask.get(), bitmap.get(),
1108 dst_rect.x, dst_rect.y,
1109 dst_rect.x, dst_rect.y,
1110 dst_rect.x, dst_rect.y,
1111 dst_rect.width, dst_rect.height);
1112
1113 pixman_image_set_transform(src_img, nullptr);
1114 }
1115
ZoomOpacityBlit(int x,int y,int ox,int oy,Bitmap const & src,Rect const & src_rect,double zoom_x,double zoom_y,Opacity const & opacity,Bitmap::BlendMode blend_mode)1116 void Bitmap::ZoomOpacityBlit(int x, int y, int ox, int oy,
1117 Bitmap const& src, Rect const& src_rect,
1118 double zoom_x, double zoom_y,
1119 Opacity const& opacity, Bitmap::BlendMode blend_mode)
1120 {
1121 if (opacity.IsTransparent()) {
1122 return;
1123 }
1124
1125 Rect dst_rect(
1126 x - static_cast<int>(std::floor(ox * zoom_x)),
1127 y - static_cast<int>(std::floor(oy * zoom_y)),
1128 static_cast<int>(std::floor(src_rect.width * zoom_x)),
1129 static_cast<int>(std::floor(src_rect.height * zoom_y)));
1130 StretchBlit(dst_rect, src, src_rect, opacity, blend_mode);
1131 }
1132
GetOperator(pixman_image_t * mask,Bitmap::BlendMode blend_mode) const1133 pixman_op_t Bitmap::GetOperator(pixman_image_t* mask, Bitmap::BlendMode blend_mode) const {
1134 if (blend_mode != BlendMode::Default) {
1135 switch (blend_mode) {
1136 case BlendMode::Normal:
1137 return PIXMAN_OP_OVER;
1138 case BlendMode::NormalWithoutAlpha:
1139 return PIXMAN_OP_SRC;
1140 case BlendMode::XOR:
1141 return PIXMAN_OP_XOR;
1142 case BlendMode::Additive:
1143 return PIXMAN_OP_ADD;
1144 case BlendMode::Multiply:
1145 return PIXMAN_OP_MULTIPLY;
1146 case BlendMode::Overlay:
1147 return PIXMAN_OP_OVERLAY;
1148 case BlendMode::Saturate:
1149 return PIXMAN_OP_SATURATE;
1150 case BlendMode::Darken:
1151 return PIXMAN_OP_DARKEN;
1152 case BlendMode::Lighten:
1153 return PIXMAN_OP_LIGHTEN;
1154 case BlendMode::ColorDodge:
1155 return PIXMAN_OP_COLOR_DODGE;
1156 case BlendMode::ColorBurn:
1157 return PIXMAN_OP_COLOR_BURN;
1158 case BlendMode::Difference:
1159 return PIXMAN_OP_DIFFERENCE;
1160 case BlendMode::Exclusion:
1161 return PIXMAN_OP_EXCLUSION;
1162 case BlendMode::SoftLight:
1163 return PIXMAN_OP_SOFT_LIGHT;
1164 case BlendMode::HardLight:
1165 return PIXMAN_OP_HARD_LIGHT;
1166 default:
1167 return PIXMAN_OP_CLEAR;
1168 }
1169 }
1170
1171 if (!mask && (!GetTransparent() || GetImageOpacity() == ImageOpacity::Opaque)) {
1172 return PIXMAN_OP_SRC;
1173 }
1174
1175 return PIXMAN_OP_OVER;
1176 }
1177
EdgeMirrorBlit(int x,int y,Bitmap const & src,Rect const & src_rect,bool mirror_x,bool mirror_y,Opacity const & opacity)1178 void Bitmap::EdgeMirrorBlit(int x, int y, Bitmap const& src, Rect const& src_rect, bool mirror_x, bool mirror_y, Opacity const& opacity) {
1179 if (opacity.IsTransparent())
1180 return;
1181
1182 auto mask = CreateMask(opacity, src_rect);
1183
1184 const auto dst_rect = GetRect();
1185
1186 auto draw = [&](int x, int y) {
1187 pixman_image_composite32(src.GetOperator(mask.get()),
1188 src.bitmap.get(),
1189 mask.get(), bitmap.get(),
1190 src_rect.x, src_rect.y,
1191 0, 0,
1192 x, y,
1193 src_rect.width, src_rect.height);
1194 };
1195
1196 draw(x, y);
1197
1198 const bool clone_x = (mirror_x && x + src_rect.width > dst_rect.width);
1199 const bool clone_y = (mirror_y && y + src_rect.height > dst_rect.height);
1200
1201 if (clone_x) {
1202 draw(x - dst_rect.width, y);
1203 }
1204
1205 if (clone_y) {
1206 draw(x, y - dst_rect.height);
1207 }
1208
1209 if (clone_x && clone_y) {
1210 draw(x - dst_rect.width, y - dst_rect.height);
1211 }
1212 }
1213
1214