@swf2es/player-hosts 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/thread/format/bitmap.js +13 -14
- package/dist/thread/format/index.js +1 -1
- package/dist/thread/format/swf.js +11 -1
- package/dist/thread/player/avm1/globals/BitmapData.js +2 -1
- package/dist/thread/player/avm1/keyboard.js +13 -0
- package/dist/thread/player/avm1/loads.js +3 -0
- package/dist/thread/player/bitmap/composite.js +767 -0
- package/dist/thread/player/bitmap/draw.js +3 -3
- package/dist/thread/player/bitmap/filters.js +32 -32
- package/dist/thread/player/bitmap/ops.js +2 -766
- package/dist/thread/player/bitmap/pixel-bender.js +4 -1
- package/dist/thread/player/display/displayobject.js +1 -1
- package/dist/thread/player/display/instantiate.js +28 -29
- package/dist/thread/player/input/keyboard.js +20 -1
- package/dist/thread/player/input/text-input.js +99 -0
- package/dist/thread/player/media/audio.js +11 -0
- package/dist/thread/player/media/sounds.js +20 -0
- package/dist/thread/player/player.js +3 -1
- package/dist/thread/player/playerglobal/flash/desktop/NativeApplication.js +1 -1
- package/dist/thread/player/playerglobal/flash/display/BitmapData.js +3 -2
- package/dist/thread/player/playerglobal/flash/display/SimpleButton.js +12 -11
- package/dist/thread/player/playerglobal/flash/display/Stage.js +2 -8
- package/dist/thread/player/playerglobal/flash/display/shaders.js +1 -1
- package/dist/thread/player/playerglobal/flash/filesystem/paths.js +1 -1
- package/dist/thread/player/playerglobal/flash/filesystem/writes.js +1 -1
- package/dist/thread/player/playerglobal/flash/globalization/locale.js +1 -1
- package/dist/thread/player/playerglobal/flash/media/Sound.js +3 -0
- package/dist/thread/player/scripting/events.js +2 -2
- package/dist/thread/player/scripting/loads.js +4 -1
- package/dist/thread/player/scripting/symbols.js +24 -30
- package/dist/thread/player/scripting.js +1 -6
- package/dist/thread/runtime/avm1/errors.js +1 -1
- package/dist/thread/runtime/avm1/numbers.js +7 -8
- package/dist/thread/runtime/avm2/amf.js +2 -1
- package/dist/thread/runtime/avm2/names.js +0 -4
- package/dist/thread/runtime/avm2/natives/bytearray.js +6 -166
- package/dist/thread/runtime/avm2/natives/regexp.js +68 -64
- package/dist/thread/runtime/avm2/natives/shell-workers.js +1 -1
- package/dist/thread/runtime/avm2/natives/shell.js +2 -1
- package/dist/thread/runtime/avm2/natives/xml/parser.js +2 -2
- package/dist/thread/runtime/avm2/numbers.js +1 -1
- package/dist/thread/runtime/avm2/runtime.js +10 -8
- package/dist/thread/runtime/avm2/traits.js +1 -1
- package/dist/thread/runtime/avm2/utf8.js +167 -0
- package/package.json +2 -2
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// the store premultiplying again as it writes. The rules are the ones
|
|
4
|
-
// Flash traces under adl and records in Ruffle's corpus.
|
|
5
|
-
import { DRAW_OPAQUE, DRAW_SKIP_ZERO_ALPHA, DRAW_SMOOTH, DRAW_TRANSFORM, DrawKernel, } from "../../native/render/index.js";
|
|
6
|
-
import { BitmapStore, over, premultiply, unmultiply } from "./bitmap.js";
|
|
7
|
-
import { bitmapKernel, drawKernel } from "./kernel.js";
|
|
1
|
+
import { premultiply, unmultiply } from "./bitmap.js";
|
|
2
|
+
import { bitmapKernel } from "./kernel.js";
|
|
8
3
|
import { perlinSetup } from "./turbulence.js";
|
|
9
4
|
const M = 2147483647;
|
|
10
5
|
/**
|
|
@@ -478,762 +473,3 @@ export function pixelDissolve(store, source, rect, dx, dy, seed, count, fill) {
|
|
|
478
473
|
store.changed();
|
|
479
474
|
return s;
|
|
480
475
|
}
|
|
481
|
-
// The blend modes draw composites with, as the numbers the pixel loops take,
|
|
482
|
-
// worked out once a draw.
|
|
483
|
-
const NORMAL = 0;
|
|
484
|
-
const MULTIPLY = 1;
|
|
485
|
-
const SCREEN = 2;
|
|
486
|
-
const LIGHTEN = 3;
|
|
487
|
-
const DARKEN = 4;
|
|
488
|
-
const DIFFERENCE = 5;
|
|
489
|
-
const ADD = 6;
|
|
490
|
-
const SUBTRACT = 7;
|
|
491
|
-
const INVERT = 8;
|
|
492
|
-
const OVERLAY = 9;
|
|
493
|
-
const HARDLIGHT = 10;
|
|
494
|
-
const ERASE = 11;
|
|
495
|
-
const ALPHA = 12;
|
|
496
|
-
/**
|
|
497
|
-
* Each blend mode draw takes, by its name; any other draws nothing. Alpha
|
|
498
|
-
* and erase need a layer: a display object drawn has one, and they then
|
|
499
|
-
* scale the destination's alpha where it covers; a BitmapData drawn has
|
|
500
|
-
* not, and they do nothing.
|
|
501
|
-
*/
|
|
502
|
-
const MODES = new Map([
|
|
503
|
-
["normal", NORMAL],
|
|
504
|
-
["layer", NORMAL],
|
|
505
|
-
["multiply", MULTIPLY],
|
|
506
|
-
["screen", SCREEN],
|
|
507
|
-
["lighten", LIGHTEN],
|
|
508
|
-
["darken", DARKEN],
|
|
509
|
-
["difference", DIFFERENCE],
|
|
510
|
-
["add", ADD],
|
|
511
|
-
["subtract", SUBTRACT],
|
|
512
|
-
["invert", INVERT],
|
|
513
|
-
["overlay", OVERLAY],
|
|
514
|
-
["hardlight", HARDLIGHT],
|
|
515
|
-
["erase", ERASE],
|
|
516
|
-
["alpha", ALPHA],
|
|
517
|
-
]);
|
|
518
|
-
/**
|
|
519
|
-
* draw of a bitmap source: each destination pixel in the clip takes the
|
|
520
|
-
* source pixel under its centre, through the matrix's inverse, clamped to
|
|
521
|
-
* the source's edges (bilinear when smoothing), scaled by how much of the
|
|
522
|
-
* pixel the transformed source covers (a 4 x 4 sample grid), passed
|
|
523
|
-
* through the colour transform as colorTransform does, and composited by
|
|
524
|
-
* the blend mode. Flash's own pixels, under adl: nearest at the centre,
|
|
525
|
-
* normal as the store's source-over, alpha and erase as nothing unless the
|
|
526
|
-
* source is a display object's `layer`: a Bitmap's, every pixel of which it
|
|
527
|
-
* covers, or a drawing's snapshot, which covers only where it drew.
|
|
528
|
-
*/
|
|
529
|
-
export function drawBitmap(store, source, m, ct, mode, clip, smooth, rows = false, layer = null) {
|
|
530
|
-
const code = MODES.get(mode);
|
|
531
|
-
const whole = code === ERASE || code === ALPHA;
|
|
532
|
-
if (code === undefined || (whole && layer === null)) {
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
// A drawing's alpha blend leaves the destination where it drew nothing, or drew clear (adl).
|
|
536
|
-
const keepClear = code === ALPHA && layer === "drawing";
|
|
537
|
-
// Flash rasterises the translation snapped down to quarter pixels, which
|
|
538
|
-
// its 4 x 4 coverage then reads exactly (a move of 0.49 covers 3/4 and
|
|
539
|
-
// 1/4 of the edge pixels under adl).
|
|
540
|
-
m = { ...m, tx: Math.floor(m.tx * 4) / 4, ty: Math.floor(m.ty * 4) / 4 };
|
|
541
|
-
const det = m.a * m.d - m.b * m.c;
|
|
542
|
-
if (det === 0) {
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
// The destination pixels the source can reach: its corners through the matrix.
|
|
546
|
-
const corners = [
|
|
547
|
-
[0, 0],
|
|
548
|
-
[source.width, 0],
|
|
549
|
-
[0, source.height],
|
|
550
|
-
[source.width, source.height],
|
|
551
|
-
].map(([x, y]) => [m.a * x + m.c * y + m.tx, m.b * x + m.d * y + m.ty]);
|
|
552
|
-
const xs = corners.map((p) => p[0]);
|
|
553
|
-
const ys = corners.map((p) => p[1]);
|
|
554
|
-
let area = store.clip({
|
|
555
|
-
x: Math.floor(Math.min(...xs)),
|
|
556
|
-
y: Math.floor(Math.min(...ys)),
|
|
557
|
-
width: Math.ceil(Math.max(...xs)) - Math.floor(Math.min(...xs)),
|
|
558
|
-
height: Math.ceil(Math.max(...ys)) - Math.floor(Math.min(...ys)),
|
|
559
|
-
});
|
|
560
|
-
if (area && clip) {
|
|
561
|
-
area = store.clip({
|
|
562
|
-
x: Math.max(area.x, clip.x),
|
|
563
|
-
y: Math.max(area.y, clip.y),
|
|
564
|
-
width: Math.min(area.x + area.width, clip.x + clip.width) - Math.max(area.x, clip.x),
|
|
565
|
-
height: Math.min(area.y + area.height, clip.y + clip.height) - Math.max(area.y, clip.y),
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
if (!area) {
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
// A layer's blend reads the source as it was: of a Bitmap of the bitmap itself, the part
|
|
572
|
-
// that can reach the area is copied first.
|
|
573
|
-
if (whole && source === store) {
|
|
574
|
-
[source, m] = reachable(store, m, area);
|
|
575
|
-
}
|
|
576
|
-
const transform = ct ? prepareColorTransform(ct) : null;
|
|
577
|
-
// Unscaled at a whole pixel, as every snapshot of a display object is
|
|
578
|
-
// composited: each covered pixel is covered whole and is the source's
|
|
579
|
-
// own, so it is copied straight, as the general path below would.
|
|
580
|
-
const moved = m.a === 1 &&
|
|
581
|
-
m.b === 0 &&
|
|
582
|
-
m.c === 0 &&
|
|
583
|
-
m.d === 1 &&
|
|
584
|
-
Number.isInteger(m.tx) &&
|
|
585
|
-
Number.isInteger(m.ty) &&
|
|
586
|
-
source !== store;
|
|
587
|
-
// The inverse: from a destination point to the source's.
|
|
588
|
-
const ia = m.d / det;
|
|
589
|
-
const ib = -m.b / det;
|
|
590
|
-
const ic = -m.c / det;
|
|
591
|
-
const id = m.a / det;
|
|
592
|
-
const itx = -(ia * m.tx + ic * m.ty);
|
|
593
|
-
const ity = -(ib * m.tx + id * m.ty);
|
|
594
|
-
if (code === NORMAL && source !== store) {
|
|
595
|
-
const inverse = job.inverse;
|
|
596
|
-
inverse[0] = ia;
|
|
597
|
-
inverse[1] = ib;
|
|
598
|
-
inverse[2] = ic;
|
|
599
|
-
inverse[3] = id;
|
|
600
|
-
inverse[4] = itx;
|
|
601
|
-
inverse[5] = ity;
|
|
602
|
-
if (drawnNatively(store, source, m, moved, area, transform, smooth)) {
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
if (moved) {
|
|
607
|
-
if (composite(store, source, area, m.tx, m.ty, transform, code, keepClear)) {
|
|
608
|
-
store.changed();
|
|
609
|
-
}
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
// How many of the pixel's 4 x 4 samples fall inside the source.
|
|
613
|
-
const coverage = (x, y) => {
|
|
614
|
-
let hits = 0;
|
|
615
|
-
for (let k = 0; k < 16; k++) {
|
|
616
|
-
const px = x + ((k & 3) + 0.5) / 4;
|
|
617
|
-
const py = y + ((k >> 2) + 0.5) / 4;
|
|
618
|
-
const sx = ia * px + ic * py + itx;
|
|
619
|
-
const sy = ib * px + id * py + ity;
|
|
620
|
-
if (sx >= 0 && sy >= 0 && sx < source.width && sy < source.height) {
|
|
621
|
-
hits++;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return hits;
|
|
625
|
-
};
|
|
626
|
-
// The arrays taken once. What the source reads as: its own pixels drawn into itself, else as
|
|
627
|
-
// a draw reads them. The store's, which bring back what the GPU drew last or copy a library
|
|
628
|
-
// bitmap's, are taken at the first pixel put, but first when the source is the store, which
|
|
629
|
-
// reads them as written.
|
|
630
|
-
let into = source === store ? store.pixels : null;
|
|
631
|
-
const pixels = source === store ? source.readable : source.drawable;
|
|
632
|
-
// Whether a pixel was put, for the store to count a change: a draw that puts none is none.
|
|
633
|
-
let wrote = false;
|
|
634
|
-
// Through a Bitmap of itself, a BitmapData is drawn in plain scan order,
|
|
635
|
-
// each pixel reading what the ones before it wrote, which smears both
|
|
636
|
-
// ways (adl): pixel by pixel. A layer's blend has a copy of it by now.
|
|
637
|
-
if (source === store && !rows) {
|
|
638
|
-
for (let y = area.y; y < area.y + area.height; y++) {
|
|
639
|
-
for (let x = area.x; x < area.x + area.width; x++) {
|
|
640
|
-
const hits = coverage(x, y);
|
|
641
|
-
if (hits === 0) {
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
const cx = x + 0.5;
|
|
645
|
-
const cy = y + 0.5;
|
|
646
|
-
const sx = ia * cx + ic * cy + itx;
|
|
647
|
-
const sy = ib * cx + id * cy + ity;
|
|
648
|
-
let p = smooth
|
|
649
|
-
? bilinear(pixels, source.width, source.height, sx - 0.5, sy - 0.5)
|
|
650
|
-
: nearest(pixels, source.width, source.height, sx, sy);
|
|
651
|
-
if (transform) {
|
|
652
|
-
p = transformed(p, transform);
|
|
653
|
-
}
|
|
654
|
-
if (hits < 16) {
|
|
655
|
-
p = scaleBy(p, hits / 16);
|
|
656
|
-
}
|
|
657
|
-
into ??= store.pixels;
|
|
658
|
-
put(store, into, y * store.width + x, code, p);
|
|
659
|
-
wrote = true;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
if (wrote) {
|
|
663
|
-
store.changed();
|
|
664
|
-
}
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
// Otherwise a row at a time, each pass over it in a loop of its own, so
|
|
668
|
-
// that no pixel crosses a call: a pixel past 2^30 a call returns is a
|
|
669
|
-
// number V8 allocates. Every pixel of a row is read before one is
|
|
670
|
-
// written, which is how Flash draws a BitmapData into itself: a move
|
|
671
|
-
// right keeps the pixels, a move down smears rows.
|
|
672
|
-
const n = area.width;
|
|
673
|
-
const buffers = takeRows(n);
|
|
674
|
-
try {
|
|
675
|
-
const { pixels: row, hits, us, vs } = buffers;
|
|
676
|
-
const w = source.width;
|
|
677
|
-
const h = source.height;
|
|
678
|
-
for (let y = area.y; y < area.y + area.height; y++) {
|
|
679
|
-
// Where all 16 samples of a pixel surely fall inside the source, and
|
|
680
|
-
// where none can: only the pixels between are tested sample by sample.
|
|
681
|
-
spanFrom = Number.NEGATIVE_INFINITY;
|
|
682
|
-
spanTo = Number.POSITIVE_INFINITY;
|
|
683
|
-
const spans = !whole && coveredSpan(ia, ic, itx, w, y) && coveredSpan(ib, id, ity, h, y);
|
|
684
|
-
const fullFrom = spans ? Math.max(area.x, spanFrom) : 0;
|
|
685
|
-
const fullTo = spans ? Math.min(area.x + n, spanTo) : 0;
|
|
686
|
-
let any = 0;
|
|
687
|
-
for (let x = area.x, k = 0; k < n; x++, k++) {
|
|
688
|
-
const cx = x + 0.5;
|
|
689
|
-
const cy = y + 0.5;
|
|
690
|
-
const sx = ia * cx + ic * cy + itx;
|
|
691
|
-
const sy = ib * cx + id * cy + ity;
|
|
692
|
-
let hit = 16;
|
|
693
|
-
if (whole) {
|
|
694
|
-
// Erase and alpha take a pixel whole where the source is at its centre, as adl
|
|
695
|
-
// draws a shape's edge for them, an edge through the centre counted on its right
|
|
696
|
-
// and below.
|
|
697
|
-
hit = sx > 0 && sy > 0 && sx <= w && sy <= h ? 16 : 0;
|
|
698
|
-
}
|
|
699
|
-
else if (x < fullFrom || x >= fullTo) {
|
|
700
|
-
hit = coverage(x, y);
|
|
701
|
-
}
|
|
702
|
-
hits[k] = hit;
|
|
703
|
-
if (hit === 0) {
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
any++;
|
|
707
|
-
us[k] = sx;
|
|
708
|
-
vs[k] = sy;
|
|
709
|
-
}
|
|
710
|
-
if (any === 0) {
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
sampleRow(pixels, w, h, us, vs, hits, row, n, smooth);
|
|
714
|
-
if (transform) {
|
|
715
|
-
transformRow(row, hits, n, transform);
|
|
716
|
-
}
|
|
717
|
-
coverRow(row, hits, n);
|
|
718
|
-
into ??= store.pixels;
|
|
719
|
-
blendRow(store, into, y * store.width + area.x, row, hits, n, code, keepClear);
|
|
720
|
-
wrote = true;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
finally {
|
|
724
|
-
giveRowsBack(buffers);
|
|
725
|
-
}
|
|
726
|
-
if (wrote) {
|
|
727
|
-
store.changed();
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
/** The draw kernel's job, filled for each draw: drawBitmap allocates nothing for it. */
|
|
731
|
-
const job = DrawKernel.job();
|
|
732
|
-
/** How far a source point may lie for the draw kernel, whose numbers are then exact as drawBitmap's. */
|
|
733
|
-
const REACH = 2 ** 30;
|
|
734
|
-
/**
|
|
735
|
-
* A draw in the normal blend mode of another store, by the draw kernel
|
|
736
|
-
* (draw.zig), whose pixels are those of the paths below, to the bit, the
|
|
737
|
-
* matrix's inverse in `job.inverse` already: whether it drew it, which it
|
|
738
|
-
* does not for numbers it cannot take as they are, nor where the realm has
|
|
739
|
-
* no kernel.
|
|
740
|
-
*/
|
|
741
|
-
function drawnNatively(store, source, m, moved, area, ct, smooth) {
|
|
742
|
-
// A store the GPU drew last is read back only by a draw that puts a pixel, as below.
|
|
743
|
-
if (store.newerOnGpu) {
|
|
744
|
-
return false;
|
|
745
|
-
}
|
|
746
|
-
// The source rows the draw reads: the area's, moved; else those its corners reach, and one
|
|
747
|
-
// more each way for smoothing and rounding. A point far out, or not finite, is drawn below.
|
|
748
|
-
const inverse = job.inverse;
|
|
749
|
-
let top;
|
|
750
|
-
let bottom;
|
|
751
|
-
if (moved) {
|
|
752
|
-
if (Math.abs(m.tx) >= 2 ** 31 || Math.abs(m.ty) >= 2 ** 31) {
|
|
753
|
-
return false;
|
|
754
|
-
}
|
|
755
|
-
top = area.y - m.ty;
|
|
756
|
-
bottom = top + area.height;
|
|
757
|
-
}
|
|
758
|
-
else {
|
|
759
|
-
const x0 = area.x;
|
|
760
|
-
const x1 = area.x + area.width;
|
|
761
|
-
const y0 = area.y;
|
|
762
|
-
const y1 = area.y + area.height;
|
|
763
|
-
const ia = inverse[0];
|
|
764
|
-
const ib = inverse[1];
|
|
765
|
-
const ic = inverse[2];
|
|
766
|
-
const id = inverse[3];
|
|
767
|
-
const itx = inverse[4];
|
|
768
|
-
const ity = inverse[5];
|
|
769
|
-
const ya = ib * x0 + id * y0 + ity;
|
|
770
|
-
const yb = ib * x1 + id * y0 + ity;
|
|
771
|
-
const yc = ib * x0 + id * y1 + ity;
|
|
772
|
-
const yd = ib * x1 + id * y1 + ity;
|
|
773
|
-
const xa = ia * x0 + ic * y0 + itx;
|
|
774
|
-
const xb = ia * x1 + ic * y0 + itx;
|
|
775
|
-
const xc = ia * x0 + ic * y1 + itx;
|
|
776
|
-
const xd = ia * x1 + ic * y1 + itx;
|
|
777
|
-
const low = Math.min(ya, yb, yc, yd);
|
|
778
|
-
const high = Math.max(ya, yb, yc, yd);
|
|
779
|
-
const far = Math.max(Math.abs(xa), Math.abs(xb), Math.abs(xc), Math.abs(xd));
|
|
780
|
-
if (!(Math.abs(low) < REACH && Math.abs(high) < REACH && far < REACH)) {
|
|
781
|
-
return false;
|
|
782
|
-
}
|
|
783
|
-
top = Math.max(0, Math.min(source.height - 1, Math.floor(low) - 1));
|
|
784
|
-
bottom = Math.max(top + 1, Math.min(source.height, Math.ceil(high) + 1));
|
|
785
|
-
}
|
|
786
|
-
if (ct) {
|
|
787
|
-
const t = job.transform;
|
|
788
|
-
t[0] = ct.rMul;
|
|
789
|
-
t[1] = ct.gMul;
|
|
790
|
-
t[2] = ct.bMul;
|
|
791
|
-
t[3] = ct.aMul;
|
|
792
|
-
t[4] = ct.rAdd;
|
|
793
|
-
t[5] = ct.gAdd;
|
|
794
|
-
t[6] = ct.bAdd;
|
|
795
|
-
t[7] = ct.aAdd;
|
|
796
|
-
if (!t.every(Number.isFinite)) {
|
|
797
|
-
return false;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
// Copying wide source rows for a small clip can cost more than the draw.
|
|
801
|
-
// Whole-pixel moves do less work per destination pixel, so their limit is lower.
|
|
802
|
-
const sourcePixels = source.width * (bottom - top);
|
|
803
|
-
const copiedPerPixel = moved ? 32 : 128;
|
|
804
|
-
if (sourcePixels > Math.max(1024, area.width * area.height * copiedPerPixel)) {
|
|
805
|
-
return false;
|
|
806
|
-
}
|
|
807
|
-
const kernel = drawKernel();
|
|
808
|
-
if (!kernel) {
|
|
809
|
-
return false;
|
|
810
|
-
}
|
|
811
|
-
job.source = source.drawable;
|
|
812
|
-
job.sourceWidth = source.width;
|
|
813
|
-
job.sourceHeight = source.height;
|
|
814
|
-
job.sourceTop = top;
|
|
815
|
-
job.sourceRows = bottom - top;
|
|
816
|
-
job.into = store.readable;
|
|
817
|
-
job.intoWidth = store.width;
|
|
818
|
-
job.x = area.x;
|
|
819
|
-
job.y = area.y;
|
|
820
|
-
job.width = area.width;
|
|
821
|
-
job.height = area.height;
|
|
822
|
-
job.moved = moved;
|
|
823
|
-
job.tx = moved ? m.tx : 0;
|
|
824
|
-
job.ty = moved ? m.ty : 0;
|
|
825
|
-
job.flags =
|
|
826
|
-
(smooth ? DRAW_SMOOTH : 0) |
|
|
827
|
-
(ct ? DRAW_TRANSFORM : 0) |
|
|
828
|
-
(store.transparent ? 0 : DRAW_OPAQUE) |
|
|
829
|
-
(ct?.skipZeroAlpha ? DRAW_SKIP_ZERO_ALPHA : 0);
|
|
830
|
-
const result = kernel.draw(job);
|
|
831
|
-
// The job holds no store's pixels past the draw.
|
|
832
|
-
job.source = EMPTY;
|
|
833
|
-
job.into = EMPTY;
|
|
834
|
-
if (result === "refused") {
|
|
835
|
-
return false;
|
|
836
|
-
}
|
|
837
|
-
if (result === "written") {
|
|
838
|
-
kernel.writeBack(job, store.pixels);
|
|
839
|
-
store.changed();
|
|
840
|
-
}
|
|
841
|
-
return true;
|
|
842
|
-
}
|
|
843
|
-
const EMPTY = new Uint32Array(0);
|
|
844
|
-
/**
|
|
845
|
-
* The row buffers kept between draws, which drawBitmap and composite take
|
|
846
|
-
* for a draw and give back after it. A draw runs to its end without
|
|
847
|
-
* calling out, so no other draw can take them in the middle of one:
|
|
848
|
-
* `scratchTaken` holds that, and a draw that finds them taken throws
|
|
849
|
-
* rather than overwrite a row another is working on.
|
|
850
|
-
*/
|
|
851
|
-
let scratch = {
|
|
852
|
-
pixels: new Uint32Array(0),
|
|
853
|
-
hits: new Uint8Array(0),
|
|
854
|
-
us: new Float64Array(0),
|
|
855
|
-
vs: new Float64Array(0),
|
|
856
|
-
};
|
|
857
|
-
let scratchTaken = false;
|
|
858
|
-
/** The widest row whose buffers are kept for the next draw: a wider one's go with its draw. */
|
|
859
|
-
const ROW_KEPT = 8192;
|
|
860
|
-
/** Buffers for rows of `n` pixels: the kept ones where they are wide enough, until given back. */
|
|
861
|
-
function takeRows(n) {
|
|
862
|
-
if (scratch.pixels.length >= n) {
|
|
863
|
-
if (scratchTaken) {
|
|
864
|
-
throw new Error("drawBitmap's row buffers are taken by a draw already under way");
|
|
865
|
-
}
|
|
866
|
-
scratchTaken = true;
|
|
867
|
-
return scratch;
|
|
868
|
-
}
|
|
869
|
-
const buffers = {
|
|
870
|
-
pixels: new Uint32Array(n),
|
|
871
|
-
hits: new Uint8Array(n),
|
|
872
|
-
us: new Float64Array(n),
|
|
873
|
-
vs: new Float64Array(n),
|
|
874
|
-
};
|
|
875
|
-
if (n <= ROW_KEPT && !scratchTaken) {
|
|
876
|
-
scratch = buffers;
|
|
877
|
-
scratchTaken = true;
|
|
878
|
-
}
|
|
879
|
-
return buffers;
|
|
880
|
-
}
|
|
881
|
-
function giveRowsBack(buffers) {
|
|
882
|
-
if (buffers === scratch) {
|
|
883
|
-
scratchTaken = false;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
/** coveredSpan's answer: the first pixel of a row covered whole and the one past the last. */
|
|
887
|
-
let spanFrom = 0;
|
|
888
|
-
let spanTo = 0;
|
|
889
|
-
/**
|
|
890
|
-
* Narrows spanFrom to spanTo, the destination pixels in row y whose 16 samples
|
|
891
|
-
* all have u = a x + c y' + t within [0, size), y' each of the row's four
|
|
892
|
-
* sample rows; false where u barely moves along x without being still,
|
|
893
|
-
* which leaves every pixel to be tested. The span keeps a margin of a
|
|
894
|
-
* thousandth of a pixel, and more where the numbers are large, from where
|
|
895
|
-
* the samples' own arithmetic decides, so a pixel in it is one the tests
|
|
896
|
-
* find whole.
|
|
897
|
-
*/
|
|
898
|
-
function coveredSpan(a, c, t, size, y) {
|
|
899
|
-
if (a === 0) {
|
|
900
|
-
// u is the same all along a sample row, worked out as the tests do.
|
|
901
|
-
for (let k = 0; k < 4; k++) {
|
|
902
|
-
const u = c * (y + (k + 0.5) / 4) + t;
|
|
903
|
-
if (!(u >= 0 && u < size)) {
|
|
904
|
-
spanFrom = Number.POSITIVE_INFINITY;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
return true;
|
|
908
|
-
}
|
|
909
|
-
if (Math.abs(a) < 1e-6) {
|
|
910
|
-
return false;
|
|
911
|
-
}
|
|
912
|
-
for (let k = 0; k < 4; k++) {
|
|
913
|
-
const offset = c * (y + (k + 0.5) / 4) + t;
|
|
914
|
-
// The x where u is 0 and where it is size, in either order.
|
|
915
|
-
const at0 = -offset / a;
|
|
916
|
-
const atSize = (size - offset) / a;
|
|
917
|
-
const margin = 0.001 +
|
|
918
|
-
(1e-12 * (Math.abs(offset) + size + Math.abs(a) * (Math.abs(at0) + Math.abs(atSize)))) /
|
|
919
|
-
Math.abs(a);
|
|
920
|
-
const low = Math.min(at0, atSize) + margin;
|
|
921
|
-
const high = Math.max(at0, atSize) - margin;
|
|
922
|
-
if (!Number.isFinite(low) || !Number.isFinite(high)) {
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
// Samples lie at x + 1/8 to x + 7/8.
|
|
926
|
-
spanFrom = Math.max(spanFrom, Math.ceil(low - 0.125));
|
|
927
|
-
spanTo = Math.min(spanTo, Math.floor(high - 0.875) + 1);
|
|
928
|
-
}
|
|
929
|
-
return true;
|
|
930
|
-
}
|
|
931
|
-
/** The source's pixel under each covered pixel's point of the row, nearest or bilinear. */
|
|
932
|
-
function sampleRow(from, width, height, us, vs, hits, row, n, smooth) {
|
|
933
|
-
for (let k = 0; k < n; k++) {
|
|
934
|
-
if (hits[k] === 0) {
|
|
935
|
-
continue;
|
|
936
|
-
}
|
|
937
|
-
row[k] = smooth
|
|
938
|
-
? bilinear(from, width, height, us[k] - 0.5, vs[k] - 0.5)
|
|
939
|
-
: nearest(from, width, height, us[k], vs[k]);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
/** The row's covered pixels through the colour transform, a run of one pixel transformed once. */
|
|
943
|
-
function transformRow(row, hits, n, ct) {
|
|
944
|
-
let last = -1;
|
|
945
|
-
let out = 0;
|
|
946
|
-
for (let k = 0; k < n; k++) {
|
|
947
|
-
if (hits[k] === 0) {
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
const p = row[k];
|
|
951
|
-
if (p !== last) {
|
|
952
|
-
last = p;
|
|
953
|
-
out = transformed(p, ct);
|
|
954
|
-
}
|
|
955
|
-
row[k] = out;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
/** The row's partly covered pixels scaled by their coverage. */
|
|
959
|
-
function coverRow(row, hits, n) {
|
|
960
|
-
for (let k = 0; k < n; k++) {
|
|
961
|
-
const hit = hits[k];
|
|
962
|
-
if (hit !== 0 && hit < 16) {
|
|
963
|
-
row[k] = scaleBy(row[k], hit / 16);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
/**
|
|
968
|
-
* The row's covered pixels composited into the store's pixels from `at` by
|
|
969
|
-
* the blend mode; with `keepClear`, a clear one leaves the destination be.
|
|
970
|
-
*/
|
|
971
|
-
function blendRow(store, into, at, row, hits, n, mode, keepClear) {
|
|
972
|
-
if (mode === NORMAL) {
|
|
973
|
-
const opaque = store.transparent ? 0 : 0xff000000;
|
|
974
|
-
for (let k = 0; k < n; k++) {
|
|
975
|
-
if (hits[k] === 0) {
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
const p = row[k];
|
|
979
|
-
const sa = p >>> 24;
|
|
980
|
-
into[at + k] = sa === 255 ? p : (over(p, into[at + k]) | opaque) >>> 0;
|
|
981
|
-
}
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
for (let k = 0; k < n; k++) {
|
|
985
|
-
if (hits[k] !== 0 && !(keepClear && row[k] >>> 24 === 0)) {
|
|
986
|
-
put(store, into, at + k, mode, row[k]);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
/** `source` at (tx, ty) composited into `area` of the store, a pixel each; whether it put one. */
|
|
991
|
-
function composite(store, source, area, tx, ty, ct, mode, keepClear) {
|
|
992
|
-
const from = source.drawable;
|
|
993
|
-
const opaque = store.transparent ? 0 : 0xff000000;
|
|
994
|
-
// The store's pixels, taken as drawBitmap takes them: at the first pixel put.
|
|
995
|
-
let into = null;
|
|
996
|
-
if (!ct && mode === NORMAL) {
|
|
997
|
-
for (let y = area.y; y < area.y + area.height; y++) {
|
|
998
|
-
let i = y * store.width + area.x;
|
|
999
|
-
let j = (y - ty) * source.width + (area.x - tx);
|
|
1000
|
-
for (let x = 0; x < area.width; x++, i++, j++) {
|
|
1001
|
-
// Source over, inline: what blend("normal") computes.
|
|
1002
|
-
const p = from[j];
|
|
1003
|
-
const sa = p >>> 24;
|
|
1004
|
-
if (sa === 0) {
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
into ??= store.pixels;
|
|
1008
|
-
into[i] = sa === 255 ? p : (over(p, into[i]) | opaque) >>> 0;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
return into !== null;
|
|
1012
|
-
}
|
|
1013
|
-
// A row at a time through the passes drawBitmap's rows take, every pixel covered.
|
|
1014
|
-
const n = area.width;
|
|
1015
|
-
const buffers = takeRows(n);
|
|
1016
|
-
try {
|
|
1017
|
-
const { pixels: row, hits } = buffers;
|
|
1018
|
-
hits.fill(16, 0, n);
|
|
1019
|
-
into = store.pixels;
|
|
1020
|
-
for (let y = area.y; y < area.y + area.height; y++) {
|
|
1021
|
-
const j = (y - ty) * source.width + (area.x - tx);
|
|
1022
|
-
// A view and a copy: a loop copying it was slower, for all a view allocates.
|
|
1023
|
-
row.set(from.subarray(j, j + n));
|
|
1024
|
-
if (ct) {
|
|
1025
|
-
transformRow(row, hits, n, ct);
|
|
1026
|
-
}
|
|
1027
|
-
blendRow(store, into, y * store.width + area.x, row, hits, n, mode, keepClear);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
finally {
|
|
1031
|
-
giveRowsBack(buffers);
|
|
1032
|
-
}
|
|
1033
|
-
return true;
|
|
1034
|
-
}
|
|
1035
|
-
/**
|
|
1036
|
-
* A drawn pixel composited into the store by the blend mode, opaque where
|
|
1037
|
-
* the store is, but for erase and alpha: an opaque bitmap holds the alpha
|
|
1038
|
-
* they leave, as Flash's memory does, and reads it back (getPixel32).
|
|
1039
|
-
*/
|
|
1040
|
-
function put(store, into, i, mode, p) {
|
|
1041
|
-
const out = blend(mode, p, into[i]);
|
|
1042
|
-
if (store.transparent) {
|
|
1043
|
-
into[i] = out;
|
|
1044
|
-
}
|
|
1045
|
-
else if (mode === ERASE || mode === ALPHA) {
|
|
1046
|
-
into[i] = out;
|
|
1047
|
-
store.holdsAlpha ||= out >>> 24 !== 255;
|
|
1048
|
-
}
|
|
1049
|
-
else {
|
|
1050
|
-
into[i] = (out | 0xff000000) >>> 0;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
/**
|
|
1054
|
-
* The part of `store` a draw of itself through `m` into `area` can read,
|
|
1055
|
-
* copied, as a layer reads it before the draw writes, and the matrix that
|
|
1056
|
-
* draws the copy where the store would be drawn: the area's corners taken
|
|
1057
|
-
* back through the matrix, a pixel wider for smoothing, within the store.
|
|
1058
|
-
*/
|
|
1059
|
-
function reachable(store, m, area) {
|
|
1060
|
-
const det = m.a * m.d - m.b * m.c;
|
|
1061
|
-
const back = [
|
|
1062
|
-
[area.x, area.y],
|
|
1063
|
-
[area.x + area.width, area.y],
|
|
1064
|
-
[area.x, area.y + area.height],
|
|
1065
|
-
[area.x + area.width, area.y + area.height],
|
|
1066
|
-
].map(([x, y]) => [
|
|
1067
|
-
(m.d * (x - m.tx) - m.c * (y - m.ty)) / det,
|
|
1068
|
-
(m.a * (y - m.ty) - m.b * (x - m.tx)) / det,
|
|
1069
|
-
]);
|
|
1070
|
-
const x0 = Math.max(0, Math.floor(Math.min(...back.map((p) => p[0]))) - 1);
|
|
1071
|
-
const y0 = Math.max(0, Math.floor(Math.min(...back.map((p) => p[1]))) - 1);
|
|
1072
|
-
const x1 = Math.min(store.width, Math.ceil(Math.max(...back.map((p) => p[0]))) + 1);
|
|
1073
|
-
const y1 = Math.min(store.height, Math.ceil(Math.max(...back.map((p) => p[1]))) + 1);
|
|
1074
|
-
const width = Math.max(0, x1 - x0);
|
|
1075
|
-
const height = Math.max(0, y1 - y0);
|
|
1076
|
-
const copy = new BitmapStore(width, height, store.transparent, 0);
|
|
1077
|
-
const from = store.readable;
|
|
1078
|
-
const into = copy.pixels;
|
|
1079
|
-
for (let y = 0; y < height; y++) {
|
|
1080
|
-
into.set(from.subarray((y0 + y) * store.width + x0, (y0 + y) * store.width + x0 + width), y * width);
|
|
1081
|
-
}
|
|
1082
|
-
copy.holdsAlpha = store.holdsAlpha;
|
|
1083
|
-
return [copy, { ...m, tx: m.tx + m.a * x0 + m.c * y0, ty: m.ty + m.b * x0 + m.d * y0 }];
|
|
1084
|
-
}
|
|
1085
|
-
function nearest(from, width, height, x, y) {
|
|
1086
|
-
const px = Math.min(width - 1, Math.max(0, Math.floor(x)));
|
|
1087
|
-
const py = Math.min(height - 1, Math.max(0, Math.floor(y)));
|
|
1088
|
-
return from[py * width + px];
|
|
1089
|
-
}
|
|
1090
|
-
/** Bilinear between the four premultiplied pixels round (x, y), clamped to the edges. */
|
|
1091
|
-
function bilinear(from, width, height, x, y) {
|
|
1092
|
-
const x0 = Math.floor(x);
|
|
1093
|
-
const y0 = Math.floor(y);
|
|
1094
|
-
const fx = x - x0;
|
|
1095
|
-
const fy = y - y0;
|
|
1096
|
-
const left = Math.min(width - 1, Math.max(0, x0));
|
|
1097
|
-
const right = Math.min(width - 1, Math.max(0, x0 + 1));
|
|
1098
|
-
const top = Math.min(height - 1, Math.max(0, y0)) * width;
|
|
1099
|
-
const bottom = Math.min(height - 1, Math.max(0, y0 + 1)) * width;
|
|
1100
|
-
const p00 = from[top + left];
|
|
1101
|
-
const p10 = from[top + right];
|
|
1102
|
-
const p01 = from[bottom + left];
|
|
1103
|
-
const p11 = from[bottom + right];
|
|
1104
|
-
const gx = 1 - fx;
|
|
1105
|
-
const gy = 1 - fy;
|
|
1106
|
-
return (((lerp4(p00, p10, p01, p11, 24, fx, fy, gx, gy) << 24) |
|
|
1107
|
-
(lerp4(p00, p10, p01, p11, 16, fx, fy, gx, gy) << 16) |
|
|
1108
|
-
(lerp4(p00, p10, p01, p11, 8, fx, fy, gx, gy) << 8) |
|
|
1109
|
-
lerp4(p00, p10, p01, p11, 0, fx, fy, gx, gy)) >>>
|
|
1110
|
-
0);
|
|
1111
|
-
}
|
|
1112
|
-
/** One channel of bilinear's four pixels, weighed by fx and fy and by gx and gy, their complements. */
|
|
1113
|
-
function lerp4(p00, p10, p01, p11, shift, fx, fy, gx, gy) {
|
|
1114
|
-
const top = ((p00 >>> shift) & 0xff) * gx + ((p10 >>> shift) & 0xff) * fx;
|
|
1115
|
-
const bottom = ((p01 >>> shift) & 0xff) * gx + ((p11 >>> shift) & 0xff) * fx;
|
|
1116
|
-
return Math.round(top * gy + bottom * fy);
|
|
1117
|
-
}
|
|
1118
|
-
/** A premultiplied pixel through a colour transform, as colorTransform computes it. */
|
|
1119
|
-
function transformed(p, ct) {
|
|
1120
|
-
const a0 = p >>> 24;
|
|
1121
|
-
if (a0 === 0 && ct.skipZeroAlpha) {
|
|
1122
|
-
return 0;
|
|
1123
|
-
}
|
|
1124
|
-
const a = colorChannel(a0, ct.aMul, ct.aAdd);
|
|
1125
|
-
return (((a << 24) |
|
|
1126
|
-
(drawColorChannel(p, a0, 16, ct.rMul, ct.rAdd, a) << 16) |
|
|
1127
|
-
(drawColorChannel(p, a0, 8, ct.gMul, ct.gAdd, a) << 8) |
|
|
1128
|
-
drawColorChannel(p, a0, 0, ct.bMul, ct.bAdd, a)) >>>
|
|
1129
|
-
0);
|
|
1130
|
-
}
|
|
1131
|
-
function prepareColorTransform(ct) {
|
|
1132
|
-
return {
|
|
1133
|
-
rMul: Math.trunc(ct.rMul * 256),
|
|
1134
|
-
gMul: Math.trunc(ct.gMul * 256),
|
|
1135
|
-
bMul: Math.trunc(ct.bMul * 256),
|
|
1136
|
-
aMul: Math.trunc(ct.aMul * 256),
|
|
1137
|
-
rAdd: Math.trunc(ct.rAdd),
|
|
1138
|
-
gAdd: Math.trunc(ct.gAdd),
|
|
1139
|
-
bAdd: Math.trunc(ct.bAdd),
|
|
1140
|
-
aAdd: Math.trunc(ct.aAdd),
|
|
1141
|
-
// The alpha-zero guard tests the original offset before truncation.
|
|
1142
|
-
skipZeroAlpha: ct.aAdd <= 0,
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
// These stay outside the pixel loops so a transform creates no per-pixel closures.
|
|
1146
|
-
function colorChannel(v, mul, add) {
|
|
1147
|
-
return Math.max(0, Math.min(255, Math.floor((v * mul) / 256) + add));
|
|
1148
|
-
}
|
|
1149
|
-
function straightChannel(p, alpha, shift) {
|
|
1150
|
-
return alpha === 0 ? 0 : Math.min(255, Math.floor((((p >>> shift) & 0xff) * 255) / alpha));
|
|
1151
|
-
}
|
|
1152
|
-
function premultipliedChannel(v, alpha) {
|
|
1153
|
-
return alpha === 255 ? v : (v * (alpha + 1)) >> 8;
|
|
1154
|
-
}
|
|
1155
|
-
function drawColorChannel(p, alpha, shift, mul, add, outAlpha) {
|
|
1156
|
-
const v = colorChannel(straightChannel(p, alpha, shift), mul, add);
|
|
1157
|
-
return premultipliedChannel(v, outAlpha);
|
|
1158
|
-
}
|
|
1159
|
-
/** A premultiplied pixel with all four channels scaled by a coverage. */
|
|
1160
|
-
function scaleBy(p, f) {
|
|
1161
|
-
return (((Math.round((p >>> 24) * f) << 24) |
|
|
1162
|
-
(Math.round(((p >>> 16) & 0xff) * f) << 16) |
|
|
1163
|
-
(Math.round(((p >>> 8) & 0xff) * f) << 8) |
|
|
1164
|
-
Math.round((p & 0xff) * f)) >>>
|
|
1165
|
-
0);
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* The destination alone, as erase and alpha leave it: each channel scaled
|
|
1169
|
-
* by k / 255, what the source's alpha leaves or is, floored, as adl's are.
|
|
1170
|
-
*/
|
|
1171
|
-
function scaleFloored(d, k) {
|
|
1172
|
-
const a = Math.floor(((d >>> 24) * k) / 255);
|
|
1173
|
-
const r = Math.floor((((d >>> 16) & 0xff) * k) / 255);
|
|
1174
|
-
const g = Math.floor((((d >>> 8) & 0xff) * k) / 255);
|
|
1175
|
-
const b = Math.floor(((d & 0xff) * k) / 255);
|
|
1176
|
-
return ((a << 24) | (r << 16) | (g << 8) | b) >>> 0;
|
|
1177
|
-
}
|
|
1178
|
-
/** x * y / 255, rounded as the blend modes' arithmetic has it. */
|
|
1179
|
-
function mul(x, y) {
|
|
1180
|
-
return (x * y + 127) / 255;
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* Premultiplied s over d by a blend mode, the W3C's compositing with each
|
|
1184
|
-
* mode's mix. Every helper takes its operands as arguments, so a pixel
|
|
1185
|
-
* allocates nothing: a closure over them would take a context each call.
|
|
1186
|
-
*/
|
|
1187
|
-
function blend(mode, s, d) {
|
|
1188
|
-
if (mode === NORMAL) {
|
|
1189
|
-
return over(s, d);
|
|
1190
|
-
}
|
|
1191
|
-
const sa = s >>> 24;
|
|
1192
|
-
if (mode === ERASE || mode === ALPHA) {
|
|
1193
|
-
return scaleFloored(d, mode === ERASE ? 255 - sa : sa);
|
|
1194
|
-
}
|
|
1195
|
-
const da = d >>> 24;
|
|
1196
|
-
const a = mode === ADD || mode === SUBTRACT
|
|
1197
|
-
? Math.min(255, sa + da)
|
|
1198
|
-
: Math.min(255, Math.round(sa + da - mul(sa, da)));
|
|
1199
|
-
return (((a << 24) |
|
|
1200
|
-
(blendChannel(mode, (s >>> 16) & 0xff, (d >>> 16) & 0xff, sa, da) << 16) |
|
|
1201
|
-
(blendChannel(mode, (s >>> 8) & 0xff, (d >>> 8) & 0xff, sa, da) << 8) |
|
|
1202
|
-
blendChannel(mode, s & 0xff, d & 0xff, sa, da)) >>>
|
|
1203
|
-
0);
|
|
1204
|
-
}
|
|
1205
|
-
/** A colour channel of blend: source cs and destination cd under alphas sa and da. */
|
|
1206
|
-
function blendChannel(mode, cs, cd, sa, da) {
|
|
1207
|
-
let b;
|
|
1208
|
-
switch (mode) {
|
|
1209
|
-
case MULTIPLY:
|
|
1210
|
-
b = mul(cs, cd);
|
|
1211
|
-
break;
|
|
1212
|
-
case SCREEN:
|
|
1213
|
-
b = (cs * da) / 255 + (cd * sa) / 255 - mul(cs, cd);
|
|
1214
|
-
break;
|
|
1215
|
-
case LIGHTEN:
|
|
1216
|
-
b = Math.max(mul(cs, da), mul(cd, sa));
|
|
1217
|
-
break;
|
|
1218
|
-
case DARKEN:
|
|
1219
|
-
b = Math.min(mul(cs, da), mul(cd, sa));
|
|
1220
|
-
break;
|
|
1221
|
-
case DIFFERENCE:
|
|
1222
|
-
b = Math.abs(mul(cs, da) - mul(cd, sa));
|
|
1223
|
-
break;
|
|
1224
|
-
case ADD:
|
|
1225
|
-
return Math.min(255, cs + cd);
|
|
1226
|
-
case SUBTRACT:
|
|
1227
|
-
return Math.max(0, cd - cs);
|
|
1228
|
-
case INVERT:
|
|
1229
|
-
// The destination inverted, the source's colour unused.
|
|
1230
|
-
return Math.max(0, Math.min(255, Math.floor(mul(255 - cd, sa) + mul(cd, 255 - sa))));
|
|
1231
|
-
case OVERLAY:
|
|
1232
|
-
b = 2 * cd <= da ? 2 * mul(cs, cd) : mul(sa, da) - 2 * mul(da - cd, sa - cs);
|
|
1233
|
-
break;
|
|
1234
|
-
default:
|
|
1235
|
-
b = 2 * cs <= sa ? 2 * mul(cs, cd) : mul(sa, da) - 2 * mul(da - cd, sa - cs);
|
|
1236
|
-
break;
|
|
1237
|
-
}
|
|
1238
|
-
return Math.max(0, Math.min(255, Math.floor(b + mul(cs, 255 - da) + mul(cd, 255 - sa))));
|
|
1239
|
-
}
|