roo_display
API Documentation for roo_display
Loading...
Searching...
No Matches
streamable_stack.cpp
Go to the documentation of this file.
2
3namespace roo_display {
4
5namespace {
6
7struct Chunk {
8 Chunk(uint16_t width, uint16_t input_mask)
9 : width_(width), input_mask_(input_mask) {}
10
11 uint16_t width_;
12 uint16_t input_mask_;
13};
14
15class Block {
16 public:
17 Block(uint16_t height) : height_(height), all_inputs_(0) {}
18
19 void AddChunk(uint16_t width, uint16_t input_mask) {
20 chunks_.emplace_back(width, input_mask);
21 all_inputs_ |= input_mask;
22 }
23
24 void Merge(uint16_t x_offset, uint16_t width, uint16_t input_mask);
25
26 private:
27 friend class Composition;
28
29 uint16_t height_;
30 uint16_t all_inputs_;
31 std::vector<Chunk> chunks_;
32};
33
34inline void Block::Merge(uint16_t x_offset, uint16_t width,
35 uint16_t input_mask) {
36 std::vector<Chunk> newchunks;
37 newchunks.reserve(chunks_.size());
38
39 auto i = chunks_.begin();
40 int16_t cursor = 0;
41 uint16_t remaining_old_width;
42 all_inputs_ |= input_mask;
43 if (x_offset > 0) {
44 while (cursor + i->width_ <= x_offset) {
45 newchunks.push_back(std::move(*i));
46 cursor += i->width_;
47 ++i;
48 }
49 remaining_old_width = i->width_;
50 if (cursor < x_offset) {
51 uint16_t w = x_offset - cursor;
52 newchunks.emplace_back(w, i->input_mask_);
53 cursor += w;
54 remaining_old_width -= w;
55 }
56 } else {
57 remaining_old_width = i->width_;
58 }
59 while (true) {
60 if (width == 0) {
61 newchunks.emplace_back(remaining_old_width, i->input_mask_);
62 cursor += remaining_old_width;
63 i++;
64 if (i == chunks_.end()) {
65 chunks_.swap(newchunks);
66 return;
67 } else {
68 remaining_old_width = i->width_;
69 }
70 } else {
71 uint16_t w = std::min(remaining_old_width, width);
72 newchunks.emplace_back(w, i->input_mask_ | input_mask);
73 if (w == remaining_old_width) {
74 i++;
75 if (i == chunks_.end()) {
76 chunks_.swap(newchunks);
77 return;
78 } else {
79 remaining_old_width = i->width_;
80 }
81 } else {
82 remaining_old_width -= w;
83 }
84 width -= w;
85 cursor += w;
86 }
87 }
88}
89
90class Program {
91 public:
92 uint16_t get(uint16_t idx) const { return prg_[idx]; }
93 std::size_t size() const { return prg_.size(); }
94
95 private:
96 friend class Composition;
97
98 std::vector<uint16_t> prg_;
99};
100
101enum Instruction {
102 LOOP = 20000,
103 RET = 20001,
104 EXIT = 30000,
105 BLANK = 10001,
106 WRITE = 10002,
107 WRITE_SINGLE = 10003,
108 SKIP = 10004
109};
110
111class Composition {
112 public:
113 Composition(const Box& bounds) : bounds_(bounds), input_count_(0) {
114 data_.emplace_back(bounds.height());
115 data_.back().AddChunk(bounds.width(), 0);
116 }
117
118 // Extents must be pre-intersected with bounds_.
119 bool Add(const Box& extents, BlendingMode blending_mode);
120
121 void Compile(Program* prg);
122
123 private:
124 Box bounds_;
125 std::vector<Box> input_extents_;
126 std::vector<BlendingMode> blending_modes_;
127 std::vector<Block> data_;
128 int input_count_;
129};
130
131// Returns true for a blending mode when a transparent source implies
132// transparent result.
133bool IsBlendingModeSourceClearing(BlendingMode blending_mode) {
134 switch (blending_mode) {
141 return true;
142 }
143 default: {
144 return false;
145 }
146 }
147}
148
149// Returns true for a blending mode when a transparent destination implies
150// transparent result.
151bool IsBlendingModeDestinationClearing(BlendingMode blending_mode) {
152 switch (blending_mode) {
159 return true;
160 }
161 default: {
162 return false;
163 }
164 }
165}
166
167inline void Composition::Compile(Program* prg) {
168 std::vector<uint16_t>* code = &prg->prg_;
169 // First, emit initial skips.
170 int i = 0;
171 for (const auto& input : input_extents_) {
172 if (input.yMin() < bounds_.yMin()) {
173 code->push_back(SKIP);
174 code->push_back(i);
175 code->push_back((bounds_.yMin() - input.yMin()) * input.width());
176 }
177 i++;
178 }
179 // Then, go block-by-block.
180 for (const auto& block : data_) {
181 // if (block.chunks_.size() == 1) {
182 // uint16_t mask = block.chunks_[0].input_mask_;
183 // uint32_t total = (uint32_t)block.chunks_[0].width_ * block.height_;
184 // if (total <= 65535) {
185 // // Optimize fully empty blocks.
186 // if (mask == 0) {
187 // code->push_back(BLANK);
188 // code->push_back(total);
189 // continue;
190 // }
191 // }
192 // // See if we can merge, which is OK if all affected inputs are
193 // // non-extending.
194 // int i = 0;
195 // while (!(mask & 1)) {
196 // ++i;
197 // mask >>= 1;
198 // }
199 // int first = i;
200 // while (true) {
201 // const auto& input = input_extents_[i];
202 // if (input.xMin() < bounds_.xMin() || input.xMax() > bounds_.xMax())
203 // break;
204 // mask >>= 1;
205 // if (mask == 0) {
206 // // Success. Merge.
207 // if (i == first) {
208 // code->push_back(WRITE_SINGLE);
209 // code->push_back(first);
210 // code->push_back(total);
211 // } else {
212 // code->push_back(WRITE);
213 // code->push_back(block.chunks_[0].input_mask_);
214 // code->push_back(total);
215 // }
216 // break;
217 // }
218 // while (true) {
219 // ++i;
220 // if (mask & 1) break;
221 // mask >>= 1;
222 // }
223 // }
224 // if (mask == 0) continue;
225 // }
226
227 // Emit the loop code.
228 code->push_back(LOOP);
229 code->push_back(block.height_);
230 // First, emit potential left skips.
231 i = 0;
232 for (const auto& input : input_extents_) {
233 if (input.xMin() < bounds_.xMin() && block.all_inputs_ & (1 << i)) {
234 code->push_back(SKIP);
235 code->push_back(i);
236 code->push_back(bounds_.xMin() - input.xMin());
237 }
238 i++;
239 }
240 // Now, the chunks.
241 for (const auto& chunk : block.chunks_) {
242 uint16_t mask = chunk.input_mask_;
243 // See if some inputs should be skipped because they get completely
244 // overwritten due to blending modes.
245 {
246 uint16_t skip_mask = mask;
247 int index = 0;
248 int max_skipped_index = 0;
249 for (int index = 0; index < this->input_count_; ++index) {
250 if ((skip_mask & 1) == 0) {
251 BlendingMode blending_mode = blending_modes_[index];
252 if (IsBlendingModeSourceClearing(blending_mode)) {
253 max_skipped_index = index;
254 }
255 }
256 skip_mask >>= 1;
257 }
258 int skip_mask_mask = ((1 << max_skipped_index) - 1);
259 skip_mask = mask & skip_mask_mask;
260 mask &= ~skip_mask_mask;
261 index = 0;
262 while (skip_mask > 0) {
263 if (skip_mask & 1) {
264 code->push_back(SKIP);
265 code->push_back(index);
266 code->push_back(chunk.width_);
267 }
268 skip_mask >>= 1;
269 }
270 }
271 // See if some inputs should be skipped because they are drawn over
272 // transparent destinations in drawing modes that result in clearance.
273 {
274 int index = 0;
275 uint16_t m = mask;
276 bool dst_clear = true;
277 while (m > 0) {
278 if ((m & 1) != 0) {
279 if (dst_clear &&
280 IsBlendingModeDestinationClearing(blending_modes_[index])) {
281 code->push_back(SKIP);
282 code->push_back(index);
283 code->push_back(chunk.width_);
284 mask &= ~(1 << index);
285 } else {
286 dst_clear = false;
287 }
288 }
289 ++index;
290 m >>= 1;
291 }
292 }
293 if (mask == 0) {
294 code->push_back(BLANK);
295 code->push_back(chunk.width_);
296 } else {
297 uint16_t mask_copy = mask;
298 int index = 0;
299 while (!(mask_copy & 1)) {
300 mask_copy >>= 1;
301 ++index;
302 }
303 if (mask_copy == 1) {
304 code->push_back(WRITE_SINGLE);
305 code->push_back(index);
306 code->push_back(chunk.width_);
307 } else {
308 code->push_back(WRITE);
309 code->push_back(mask);
310 code->push_back(chunk.width_);
311 }
312 }
313 }
314 // Finally, emit potential right skips.
315 i = 0;
316 for (const auto& input : input_extents_) {
317 if (input.xMax() > bounds_.xMax() && block.all_inputs_ & (1 << i)) {
318 code->push_back(SKIP);
319 code->push_back(i);
320 code->push_back(input.xMax() - bounds_.xMax());
321 }
322 i++;
323 }
324 code->push_back(RET);
325 }
326 code->push_back(EXIT);
327}
328
329class Engine {
330 public:
331 Engine(const Program* program) : program_(program), pc_(0) {}
332
333 Instruction fetch() {
334 while (true) {
335 Instruction i = (Instruction)program_->get(pc_++);
336 switch (i) {
337 case LOOP: {
338 loop_counter_ = program_->get(pc_++);
339 loop_ret_ = pc_;
340 continue;
341 }
342 case RET: {
343 --loop_counter_;
344 if (loop_counter_ > 0) {
345 pc_ = loop_ret_;
346 }
347 continue;
348 }
349 default:
350 return i;
351 }
352 }
353 }
354
355 uint16_t read_word() { return program_->get(pc_++); }
356
357 private:
358 const Program* program_;
359 uint16_t pc_;
360 uint16_t loop_ret_;
361 uint16_t loop_counter_;
362};
363
364inline bool Composition::Add(const Box& extents, BlendingMode blending_mode) {
365 input_extents_.push_back(extents);
366 blending_modes_.push_back(blending_mode);
367 int input_idx = input_count_;
368 uint16_t input_mask = 1 << input_idx;
369 input_count_++;
370 // Box extents = Box::Intersect(bounds_, full_extents);
371 if (extents.empty()) return false;
372 std::vector<Block> newdata;
373 newdata.reserve(data_.capacity());
374 auto i = data_.begin();
375 int16_t cursor;
376 uint16_t remaining_old_height;
377 uint16_t remaining_new_height = extents.height();
378 cursor = bounds_.yMin();
379 while (cursor + i->height_ <= extents.yMin()) {
380 newdata.push_back(std::move(*i));
381 cursor += i->height_;
382 i++;
383 }
384 remaining_old_height = i->height_;
385 if (cursor < extents.yMin()) {
386 uint16_t height = extents.yMin() - cursor;
387 newdata.push_back(*i);
388 newdata.back().height_ = height;
389 cursor += height;
390 remaining_old_height -= height;
391 }
392 while (true) {
393 if (remaining_new_height == 0) {
394 newdata.push_back(std::move(*i));
395 newdata.back().height_ = remaining_old_height;
396 cursor += remaining_old_height;
397 i++;
398 if (i == data_.end()) {
399 data_.swap(newdata);
400 return true;
401 }
402 remaining_old_height = i->height_;
403 } else {
404 uint16_t height = std::min(remaining_old_height, remaining_new_height);
405 if (height == remaining_old_height) {
406 newdata.push_back(std::move(*i));
407 i++;
408 remaining_old_height = (i == data_.end()) ? 0 : i->height_;
409 } else {
410 newdata.push_back(*i);
411 remaining_old_height -= height;
412 }
413 remaining_new_height -= height;
414 newdata.back().height_ = height;
415 newdata.back().Merge(extents.xMin() - bounds_.xMin(), extents.width(),
416 input_mask);
417 if (remaining_old_height == 0) {
418 data_.swap(newdata);
419 return true;
420 }
421 cursor += height;
422 }
423 }
424}
425
426void WriteRect(Engine* engine, const Box& bounds,
427 internal::BufferingStream* streams,
428 const BlendingMode* blending_modes, const Surface& s) {
429 s.out().setAddress(bounds, s.blending_mode());
430 BufferedColorWriter writer(s.out());
431 while (true) {
432 switch (engine->fetch()) {
433 case EXIT: {
434 return;
435 }
436 case BLANK: {
437 uint16_t count = engine->read_word();
438 writer.writeColorN(s.bgcolor(), count);
439 break;
440 }
441 case SKIP: {
442 uint16_t input = engine->read_word();
443 uint16_t count = engine->read_word();
444 streams[input].skip(count);
445 break;
446 }
447 case WRITE_SINGLE: {
448 uint16_t input = engine->read_word();
449 uint16_t count = engine->read_word();
450 do {
451 Color* buf = writer.buffer_ptr();
452 uint16_t batch = writer.remaining_buffer_space();
453 if (batch > count) batch = count;
454 if (s.bgcolor() == color::Transparent) {
455 streams[input].read(buf, batch);
456 } else {
457 FillColor(buf, batch, s.bgcolor());
458 streams[input].blend(buf, batch, blending_modes[input]);
459 }
460 writer.advance_buffer_ptr(batch);
461 count -= batch;
462 } while (count > 0);
463 break;
464 }
465 case WRITE: {
466 uint16_t inputs = engine->read_word();
467 uint16_t count = engine->read_word();
468 do {
469 uint16_t input = 0;
470 uint16_t input_mask = inputs;
471 Color* buf = writer.buffer_ptr();
472 uint16_t batch = writer.remaining_buffer_space();
473 if (batch > count) batch = count;
474 while (true) {
475 if (input_mask & 1) {
476 streams[input].read(buf, batch);
477 break;
478 }
479 input++;
480 input_mask >>= 1;
481 }
482 while (true) {
483 input++;
484 input_mask >>= 1;
485 if (input_mask == 0) break;
486 if (input_mask & 1) {
487 streams[input].blend(buf, batch, blending_modes[input]);
488 }
489 }
490 // NOTE(dawidk): alpha-blending is expensive, and it is usually
491 // better to blend all inputs together, and only then blend the
492 // results onto the background.
493 if (s.bgcolor() != color::Transparent) {
494 for (int i = 0; i < batch; ++i) {
495 buf[i] = AlphaBlend(s.bgcolor(), buf[i]);
496 }
497 }
498 writer.advance_buffer_ptr(batch);
499 count -= batch;
500 } while (count > 0);
501 break;
502 }
503 default: {
504 // Unexpected.
505 return;
506 }
507 }
508 }
509}
510
511void WriteVisible(Engine* engine, const Box& bounds,
512 internal::BufferingStream* streams,
513 const BlendingMode* blending_modes, const Surface& s) {
514 BufferedPixelWriter writer(s.out(), s.blending_mode());
515 uint16_t x = bounds.xMin();
516 uint16_t y = bounds.yMin();
517 while (true) {
518 switch (engine->fetch()) {
519 case EXIT: {
520 return;
521 }
522 case BLANK: {
523 uint16_t count = engine->read_word();
524 x += count;
525 if (x > bounds.xMax()) {
526 y += (x - bounds.xMin()) / bounds.width();
527 x = (x - bounds.xMin()) % bounds.width() + bounds.xMin();
528 }
529 break;
530 }
531 case SKIP: {
532 uint16_t input = engine->read_word();
533 uint16_t count = engine->read_word();
534 streams[input].skip(count);
535 break;
536 }
537 case WRITE_SINGLE: {
538 uint16_t input = engine->read_word();
539 uint16_t count = engine->read_word();
540 if (s.bgcolor() == color::Transparent) {
541 while (count-- > 0) {
542 Color c = streams[input].next();
543 if (c.a() != 0) {
544 writer.writePixel(x, y, c);
545 }
546 ++x;
547 if (x > bounds.xMax()) {
548 x = bounds.xMin();
549 ++y;
550 }
551 }
552 } else {
553 while (count-- > 0) {
554 Color c = streams[input].next();
555 if (c.a() != 0) {
556 writer.writePixel(x, y, AlphaBlend(s.bgcolor(), c));
557 }
558 ++x;
559 if (x > bounds.xMax()) {
560 x = bounds.xMin();
561 ++y;
562 }
563 }
564 }
565 break;
566 }
567 case WRITE: {
568 uint16_t inputs = engine->read_word();
569 uint16_t count = engine->read_word();
570 Color buf[kPixelWritingBufferSize];
571 do {
572 uint16_t input = 0;
573 uint16_t input_mask = inputs;
574 uint16_t batch = kPixelWritingBufferSize;
575 if (batch > count) batch = count;
576 while (true) {
577 if (input_mask & 1) {
578 streams[input].read(buf, batch);
579 break;
580 }
581 input++;
582 input_mask >>= 1;
583 }
584 while (true) {
585 input++;
586 input_mask >>= 1;
587 if (input_mask == 0) break;
588 if (input_mask & 1) {
589 streams[input].blend(buf, batch, blending_modes[input]);
590 }
591 }
592 // NOTE(dawidk): alpha-blending is expensive, and it is usually
593 // better to blend all inputs together, and only then blend the
594 // results onto the background.
595 if (s.bgcolor() == color::Transparent) {
596 for (int i = 0; i < batch; ++i) {
597 if (buf[i].a() != 0) {
598 writer.writePixel(x, y, buf[i]);
599 }
600 ++x;
601 if (x > bounds.xMax()) {
602 x = bounds.xMin();
603 ++y;
604 }
605 }
606 } else {
607 for (int i = 0; i < batch; ++i) {
608 if (buf[i].a() != 0) {
609 writer.writePixel(x, y, AlphaBlend(s.bgcolor(), buf[i]));
610 }
611 ++x;
612 if (x > bounds.xMax()) {
613 x = bounds.xMin();
614 ++y;
615 }
616 }
617 }
618 count -= batch;
619 } while (count > 0);
620 break;
621 }
622 default: {
623 // Unexpected.
624 return;
625 }
626 }
627 }
628}
629
630class StreamableComboStream : public PixelStream {
631 public:
632 StreamableComboStream(Program prg,
633 std::vector<internal::BufferingStream> streams,
634 std::vector<BlendingMode> blending_modes)
635 : prg_(std::move(prg)),
636 engine_(&prg_),
637 streams_(std::move(streams)),
638 blending_modes_(std::move(blending_modes)),
639 remaining_count_(0) {}
640
641 void Read(Color* buf, uint16_t size) override {
642 Color* result = buf;
643 do {
644 while (remaining_count_ == 0) {
645 last_instruction_ = engine_.fetch();
646 switch (last_instruction_) {
647 case EXIT: {
648 return;
649 }
650 case BLANK: {
651 remaining_count_ = engine_.read_word();
652 break;
653 }
654 case SKIP: {
655 uint16_t input = engine_.read_word();
656 uint16_t count = engine_.read_word();
657 streams_[input].skip(count);
658 continue;
659 }
660 case WRITE_SINGLE: {
661 input_ = engine_.read_word();
662 remaining_count_ = engine_.read_word();
663 break;
664 }
665 case WRITE: {
666 input_ = engine_.read_word();
667 remaining_count_ = engine_.read_word();
668 break;
669 }
670 default: {
671 // Unexpected.
672 return;
673 }
674 }
675 }
676 uint16_t batch = std::min(size, remaining_count_);
677 switch (last_instruction_) {
678 case BLANK: {
679 FillColor(result, batch, color::Transparent);
680 break;
681 }
682 case WRITE_SINGLE: {
683 streams_[input_].read(result, batch);
684 break;
685 }
686 case WRITE: {
687 uint16_t input = 0;
688 uint16_t input_mask = input_;
689 while (true) {
690 if (input_mask & 1) {
691 streams_[input].read(result, batch);
692 break;
693 }
694 input++;
695 input_mask >>= 1;
696 }
697 while (true) {
698 input++;
699 input_mask >>= 1;
700 if (input_mask == 0) break;
701 if (input_mask & 1) {
702 streams_[input].blend(result, batch, blending_modes_[input]);
703 }
704 }
705 break;
706 }
707 default: {
708 // Unexpected.
709 break;
710 }
711 }
712 result += batch;
713 size -= batch;
714 remaining_count_ -= batch;
715 } while (size > 0);
716 }
717
718 private:
719 Program prg_;
720 Engine engine_;
721 std::vector<internal::BufferingStream> streams_;
722 std::vector<BlendingMode> blending_modes_;
723 Instruction last_instruction_;
724 uint16_t input_;
725 uint16_t remaining_count_;
726};
727
728} // namespace
729
730void StreamableStack::drawTo(const Surface& s) const {
731 Box bounds = Box::Intersect(s.clip_box(), extents_.translate(s.dx(), s.dy()));
732 if (bounds.empty()) return;
733 std::vector<internal::BufferingStream> streams;
734 std::vector<BlendingMode> blending_modes;
735 Composition composition(bounds);
736 for (const auto& input : inputs_) {
737 Box extents =
738 Box::Intersect(input.extents(), bounds.translate(-s.dx(), -s.dy()));
739 if (composition.Add(extents.translate(s.dx(), s.dy()),
740 input.blending_mode())) {
741 streams.emplace_back(input.createStream(extents), extents.area());
742 } else {
743 streams.emplace_back(nullptr, 0);
744 }
745 blending_modes.push_back(input.blending_mode());
746 }
747
748 Program prg;
749 composition.Compile(&prg);
750 Engine engine(&prg);
751 if (s.fill_mode() == FillMode::kExtents) {
752 WriteRect(&engine, bounds, &*streams.begin(), &*blending_modes.begin(), s);
753 } else {
754 WriteVisible(&engine, bounds, &*streams.begin(), &*blending_modes.begin(),
755 s);
756 }
757}
758
759std::unique_ptr<PixelStream> StreamableStack::createStream() const {
760 Box bounds = extents();
761 std::vector<internal::BufferingStream> streams;
762 std::vector<BlendingMode> blending_modes;
763 Composition composition(bounds);
764 for (const auto& input : inputs_) {
765 Box extents = Box::Intersect(input.extents(), bounds);
766 if (composition.Add(extents, input.blending_mode())) {
767 streams.emplace_back(input.createStream(extents), extents.area());
768 } else {
769 streams.emplace_back(nullptr, 0);
770 }
771 blending_modes.push_back(input.blending_mode());
772 }
773
774 Program prg;
775 composition.Compile(&prg);
776 return std::unique_ptr<PixelStream>(new StreamableComboStream(
777 std::move(prg), std::move(streams), std::move(blending_modes)));
778}
779
780std::unique_ptr<PixelStream> StreamableStack::createStream(
781 const Box& clip_box) const {
782 Box bounds = Box::Intersect(extents(), clip_box);
783 std::vector<internal::BufferingStream> streams;
784 std::vector<BlendingMode> blending_modes;
785 Composition composition(bounds);
786 for (const auto& input : inputs_) {
787 Box extents = Box::Intersect(input.extents(), bounds);
788 if (composition.Add(extents, input.blending_mode())) {
789 streams.emplace_back(input.createStream(extents), extents.area());
790 } else {
791 streams.emplace_back(nullptr, 0);
792 }
793 blending_modes.push_back(input.blending_mode());
794 }
795
796 Program prg;
797 composition.Compile(&prg);
798 return std::unique_ptr<PixelStream>(new StreamableComboStream(
799 std::move(prg), std::move(streams), std::move(blending_modes)));
800}
801
802} // namespace roo_display
BufferedRectWriter & writer
Axis-aligned integer rectangle.
Definition box.h:12
int16_t xMin() const
Minimum x (inclusive).
Definition box.h:65
int16_t xMax() const
Maximum x (inclusive).
Definition box.h:71
Box translate(int16_t x_offset, int16_t y_offset) const
Return a translated copy of this box.
Definition box.h:127
int32_t area() const
Area in pixels.
Definition box.h:83
static Box Intersect(const Box &a, const Box &b)
Return the intersection of two boxes (may be empty).
Definition box.h:25
int16_t yMin() const
Minimum y (inclusive).
Definition box.h:68
void writePixel(int16_t x, int16_t y, Color color)
Box extents() const override
Return the overall extents of the stack.
std::unique_ptr< PixelStream > createStream() const override
Create a stream for the full stack.
Defines 140 opaque HTML named colors.
BlendingMode
Porter-Duff style blending modes.
Definition blending.h:17
@ kDestinationIn
Destination which overlaps the source, replaces the source.
@ kDestinationOut
Destination is placed, where it falls outside of the source.
@ kDestination
Only the destination will be present.
@ kSource
The new ARGB8888 value completely replaces the old one.
@ kDestinationAtop
Destination which overlaps the source replaces the source. Source is placed elsewhere.
@ kSourceIn
The source that overlaps the destination, replaces the destination.
@ kSourceAtop
Source which overlaps the destination, replaces the destination. Destination is placed elsewhere.
@ kClear
No regions are enabled.
@ kSourceOut
Source is placed, where it falls outside of the destination.
Color AlphaBlend(Color bgc, Color fgc)
Definition blending.h:598
static const uint8_t kPixelWritingBufferSize
void FillColor(Color *buf, uint32_t count, Color color)
Fill an array with a single color.
Definition color.h:109
@ kExtents
Fill the entire extents box (possibly with fully transparent pixels).
BlendingMode blending_mode
Definition smooth.cpp:888
uint16_t input_mask_
uint16_t width_