Reimplemented masks and mattes (#1135)
This should greatly improve the compatibility of multiple add and subtract masks and adds support for inverted masks and intersect masks.
diff --git a/After Effects Samples/Masks.aep b/After Effects Samples/Masks.aep
new file mode 100644
index 0000000..08d8e7c
--- /dev/null
+++ b/After Effects Samples/Masks.aep
Binary files differ
diff --git a/LottieSample/src/main/assets/Tests/MaskInv.json b/LottieSample/src/main/assets/Tests/MaskInv.json
index 9c30721..9b5786f 100644
--- a/LottieSample/src/main/assets/Tests/MaskInv.json
+++ b/LottieSample/src/main/assets/Tests/MaskInv.json
@@ -1 +1,222 @@
-{"v":"4.11.1","fr":29.9700012207031,"ip":0,"op":900.000036657751,"w":400,"h":400,"nm":"MaskInv","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":1,"nm":"Background","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[50.031,0],[0,-50.031],[-50.031,0],[0,50.031]],"o":[[-50.031,0],[0,50.031],[50.031,0],[0,-50.031]],"v":[[150.59,88],[60,178.59],[150.59,269.18],[241.18,178.59]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"},{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[55.912,0],[0,-55.912],[-55.912,0],[0,55.912]],"o":[[-55.912,0],[0,55.912],[55.912,0],[0,-55.912]],"v":[[221.238,126],[120,227.238],[221.238,328.477],[322.477,227.238]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"sw":400,"sh":400,"sc":"#ff0000","ip":0,"op":900.000036657751,"st":0,"bm":0}]}
\ No newline at end of file
+{
+ "v": "4.11.1",
+ "fr": 29.9700012207031,
+ "ip": 0,
+ "op": 900.000036657751,
+ "w": 400,
+ "h": 400,
+ "nm": "MaskInv",
+ "ddd": 0,
+ "assets": [],
+ "layers": [
+ {
+ "ddd": 0,
+ "ind": 1,
+ "ty": 1,
+ "nm": "Background",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 0,
+ 0,
+ 0
+ ],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0,
+ 0
+ ],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 100,
+ 100,
+ 100
+ ],
+ "ix": 6
+ }
+ },
+ "ao": 0,
+ "hasMask": true,
+ "masksProperties": [
+ {
+ "inv": false,
+ "mode": "a",
+ "pt": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 50.031,
+ 0
+ ],
+ [
+ 0,
+ -50.031
+ ],
+ [
+ -50.031,
+ 0
+ ],
+ [
+ 0,
+ 50.031
+ ]
+ ],
+ "o": [
+ [
+ -50.031,
+ 0
+ ],
+ [
+ 0,
+ 50.031
+ ],
+ [
+ 50.031,
+ 0
+ ],
+ [
+ 0,
+ -50.031
+ ]
+ ],
+ "v": [
+ [
+ 150.59,
+ 88
+ ],
+ [
+ 60,
+ 178.59
+ ],
+ [
+ 150.59,
+ 269.18
+ ],
+ [
+ 241.18,
+ 178.59
+ ]
+ ],
+ "c": true
+ },
+ "ix": 1
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 3
+ },
+ "x": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "nm": "Mask 2"
+ },
+ {
+ "inv": false,
+ "mode": "s",
+ "pt": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 55.912,
+ 0
+ ],
+ [
+ 0,
+ -55.912
+ ],
+ [
+ -55.912,
+ 0
+ ],
+ [
+ 0,
+ 55.912
+ ]
+ ],
+ "o": [
+ [
+ -55.912,
+ 0
+ ],
+ [
+ 0,
+ 55.912
+ ],
+ [
+ 55.912,
+ 0
+ ],
+ [
+ 0,
+ -55.912
+ ]
+ ],
+ "v": [
+ [
+ 221.238,
+ 126
+ ],
+ [
+ 120,
+ 227.238
+ ],
+ [
+ 221.238,
+ 328.477
+ ],
+ [
+ 322.477,
+ 227.238
+ ]
+ ],
+ "c": true
+ },
+ "ix": 1
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 3
+ },
+ "x": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "nm": "Mask 1"
+ }
+ ],
+ "sw": 400,
+ "sh": 400,
+ "sc": "#ff0000",
+ "ip": 0,
+ "op": 900.000036657751,
+ "st": 0,
+ "bm": 0
+ }
+ ]
+}
\ No newline at end of file
diff --git a/LottieSample/src/main/assets/Tests/Masks.json b/LottieSample/src/main/assets/Tests/Masks.json
new file mode 100644
index 0000000..c7daa76
--- /dev/null
+++ b/LottieSample/src/main/assets/Tests/Masks.json
@@ -0,0 +1 @@
+{"v":"5.3.4","fr":29.9700012207031,"ip":0,"op":61.0000024845809,"w":200,"h":300,"nm":"Mask","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[205.5,320.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":true,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-79,-78],[-79,-38],[-39,-38],[-39,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"},{"inv":true,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[205.5,251.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-79,-78],[-79,-38],[-39,-38],[-39,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"},{"inv":true,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[205.5,192.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":true,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-79,-78],[-79,-38],[-39,-38],[-39,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"},{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[108.5,319.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":true,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"},{"inv":true,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-79,-78],[-79,-38],[-39,-38],[-39,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[108.5,258.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"},{"inv":true,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-79,-78],[-79,-38],[-39,-38],[-39,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[108.5,190.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":true,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"},{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-79,-78],[-79,-38],[-39,-38],[-39,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[108.5,105.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"},{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-78.75,-78],[-78.75,-38],[-38.75,-38],[-38.75,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[176.5,105.5,0],"e":[235,105.5,0],"to":[9.75,0,0],"ti":[-9.75,0,0]},{"t":60.0000024438501}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-79,-78],[-79,-38],[-39,-38],[-39,-78]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 2"},{"inv":false,"mode":"s","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-113,-110.622],[-113,-56],[-61,-56.878],[-61,-111.5]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[50,50],"ix":2},"p":{"a":0,"k":[-83,-72],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.658,5.658],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61.0000024845809,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
index 846a7c4..3ef4f62 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
@@ -5,9 +5,13 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
@@ -288,6 +292,15 @@
return PixelFormat.TRANSLUCENT;
}
+ private void drawRect(Canvas canvas, float lp, float tp, float rp, float bp, @Nullable PorterDuff.Mode mode, int color) {
+ Paint paint = new Paint();
+ paint.setColor(color);
+ if (mode != null) {
+ paint.setXfermode(new PorterDuffXfermode(mode));
+ }
+ canvas.drawRect(canvas.getWidth() * lp, canvas.getHeight() * tp, canvas.getWidth() * rp, canvas.getHeight() * bp, paint);
+ }
+
@Override
public void draw(@NonNull Canvas canvas) {
isDirty = false;
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/LPaint.java b/lottie/src/main/java/com/airbnb/lottie/animation/LPaint.java
index 0462a1d..93869aa 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/LPaint.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/LPaint.java
@@ -1,6 +1,8 @@
package com.airbnb.lottie.animation;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.os.LocaleList;
import androidx.annotation.NonNull;
@@ -12,12 +14,23 @@
*/
public class LPaint extends Paint {
public LPaint() {
+ super();
}
public LPaint(int flags) {
super(flags);
}
+ public LPaint(PorterDuff.Mode porterDuffMode) {
+ super();
+ setXfermode(new PorterDuffXfermode(porterDuffMode));
+ }
+
+ public LPaint(int flags, PorterDuff.Mode porterDuffMode) {
+ super(flags);
+ setXfermode(new PorterDuffXfermode(porterDuffMode));
+ }
+
@Override
public void setTextLocales(@NonNull LocaleList locales) {
// Do nothing.
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java b/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java
index 92cc94f..2dfc9f5 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/content/Mask.java
@@ -13,11 +13,13 @@
private final MaskMode maskMode;
private final AnimatableShapeValue maskPath;
private final AnimatableIntegerValue opacity;
+ private final boolean inverted;
- public Mask(MaskMode maskMode, AnimatableShapeValue maskPath, AnimatableIntegerValue opacity) {
+ public Mask(MaskMode maskMode, AnimatableShapeValue maskPath, AnimatableIntegerValue opacity, boolean inverted) {
this.maskMode = maskMode;
this.maskPath = maskPath;
this.opacity = opacity;
+ this.inverted = inverted;
}
public MaskMode getMaskMode() {
@@ -31,4 +33,8 @@
public AnimatableIntegerValue getOpacity() {
return opacity;
}
+
+ public boolean isInverted() {
+ return inverted;
+ }
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
index b7f5622..988408a 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
@@ -19,6 +19,7 @@
import com.airbnb.lottie.model.KeyPath;
import com.airbnb.lottie.model.KeyPathElement;
import com.airbnb.lottie.model.content.Mask;
+import com.airbnb.lottie.model.content.ShapeData;
import com.airbnb.lottie.value.LottieValueCallback;
import java.util.ArrayList;
@@ -31,14 +32,14 @@
* These flags were in Canvas but they were deprecated and removed.
* TODO: test removing these on older versions of Android.
*/
- private static final int CLIP_SAVE_FLAG = 0x02;
- private static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;
- private static final int MATRIX_SAVE_FLAG = 0x01;
- private static final int SAVE_FLAGS = CLIP_SAVE_FLAG | CLIP_TO_LAYER_SAVE_FLAG | MATRIX_SAVE_FLAG;
+ private static final int CLIP_SAVE_FLAG = 0x02;
+ private static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;
+ private static final int MATRIX_SAVE_FLAG = 0x01;
+ private static final int SAVE_FLAGS = CLIP_SAVE_FLAG | CLIP_TO_LAYER_SAVE_FLAG | MATRIX_SAVE_FLAG;
@Nullable
static BaseLayer forModel(
- Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
+ Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
switch (layerModel.getLayerType()) {
case SHAPE:
return new ShapeLayer(drawable, layerModel);
@@ -64,10 +65,14 @@
private final Path path = new Path();
private final Matrix matrix = new Matrix();
private final Paint contentPaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint dstInPaint = new LPaint(Paint.ANTI_ALIAS_FLAG, PorterDuff.Mode.DST_IN);
private final Paint addMaskPaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
- private final Paint subtractMaskPaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint addInvMaskLayerPaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint addInvMaskPaintClear = new LPaint(Paint.ANTI_ALIAS_FLAG, PorterDuff.Mode.CLEAR);
+ private final Paint subtractMaskStartingPaintNormal = new LPaint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint subtractMaskPaintDstOut = new LPaint(Paint.ANTI_ALIAS_FLAG, PorterDuff.Mode.DST_OUT);
private final Paint mattePaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
- private final Paint clearPaint = new LPaint();
+ private final Paint clearPaint = new LPaint(PorterDuff.Mode.CLEAR);
private final RectF rect = new RectF();
private final RectF maskBoundsRect = new RectF();
private final RectF matteBoundsRect = new RectF();
@@ -76,13 +81,16 @@
final Matrix boundsMatrix = new Matrix();
final LottieDrawable lottieDrawable;
final Layer layerModel;
- @Nullable private MaskKeyframeAnimation mask;
- @Nullable private BaseLayer matteLayer;
+ @Nullable
+ private MaskKeyframeAnimation mask;
+ @Nullable
+ private BaseLayer matteLayer;
/**
* This should only be used by {@link #buildParentLayerListIfNeeded()}
* to construct the list of parent layers.
*/
- @Nullable private BaseLayer parentLayer;
+ @Nullable
+ private BaseLayer parentLayer;
private List<BaseLayer> parentLayers;
private final List<BaseKeyframeAnimation<?, ?>> animations = new ArrayList<>();
@@ -93,9 +101,8 @@
this.lottieDrawable = lottieDrawable;
this.layerModel = layerModel;
drawTraceName = layerModel.getName() + "#draw";
- clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- addMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
- subtractMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ addMaskPaint.setColor(Color.BLUE);
+ subtractMaskStartingPaintNormal.setColor(Color.BLACK);
if (layerModel.getMatteType() == Layer.MatteType.INVERT) {
mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
} else {
@@ -120,7 +127,8 @@
setupInOutAnimations();
}
- @Override public void onValueChanged() {
+ @Override
+ public void onValueChanged() {
invalidateSelf();
}
@@ -146,7 +154,8 @@
new FloatKeyframeAnimation(layerModel.getInOutKeyframes());
inOutAnimation.setIsDiscrete();
inOutAnimation.addUpdateListener(new BaseKeyframeAnimation.AnimationListener() {
- @Override public void onValueChanged() {
+ @Override
+ public void onValueChanged() {
setVisible(inOutAnimation.getFloatValue() == 1f);
}
});
@@ -176,8 +185,10 @@
animations.add(newAnimation);
}
- @CallSuper @Override public void getBounds(
- RectF outBounds, Matrix parentMatrix, boolean applyParents) {
+ @CallSuper
+ @Override
+ public void getBounds(
+ RectF outBounds, Matrix parentMatrix, boolean applyParents) {
rect.set(0, 0, 0, 0);
buildParentLayerListIfNeeded();
boundsMatrix.set(parentMatrix);
@@ -308,9 +319,10 @@
// canvas so we can't use the mask bounds.
return;
case MASK_MODE_INTERSECT:
- // TODO
- return;
case MASK_MODE_ADD:
+ if (mask.isInverted()) {
+ return;
+ }
default:
path.computeBounds(tempMaskBoundsRect, false);
// As we iterate through the masks, we want to calculate the union region of the masks.
@@ -320,10 +332,10 @@
maskBoundsRect.set(tempMaskBoundsRect);
} else {
maskBoundsRect.set(
- Math.min(maskBoundsRect.left, tempMaskBoundsRect.left),
- Math.min(maskBoundsRect.top, tempMaskBoundsRect.top),
- Math.max(maskBoundsRect.right, tempMaskBoundsRect.right),
- Math.max(maskBoundsRect.bottom, tempMaskBoundsRect.bottom)
+ Math.min(maskBoundsRect.left, tempMaskBoundsRect.left),
+ Math.min(maskBoundsRect.top, tempMaskBoundsRect.top),
+ Math.max(maskBoundsRect.right, tempMaskBoundsRect.right),
+ Math.max(maskBoundsRect.bottom, tempMaskBoundsRect.bottom)
);
}
}
@@ -357,67 +369,95 @@
abstract void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha);
private void applyMasks(Canvas canvas, Matrix matrix) {
- applyMasks(canvas, matrix, Mask.MaskMode.MASK_MODE_ADD);
- // Treat intersect masks like add masks. This is not correct but it's closer.
- applyMasks(canvas, matrix, Mask.MaskMode.MASK_MODE_INTERSECT);
- applyMasks(canvas, matrix, Mask.MaskMode.MASK_MODE_SUBTRACT);
- }
-
- private void applyMasks(Canvas canvas, Matrix matrix,
- Mask.MaskMode maskMode) {
- Paint paint;
- switch (maskMode) {
- case MASK_MODE_SUBTRACT:
- paint = subtractMaskPaint;
- break;
- case MASK_MODE_INTERSECT:
- case MASK_MODE_ADD:
- default:
- // As a hack, we treat all non-subtract masks like add masks. This is not correct but it's
- // better than nothing.
- paint = addMaskPaint;
- }
-
- //noinspection ConstantConditions
- int size = mask.getMasks().size();
-
- boolean hasMask = false;
- for (int i = 0; i < size; i++) {
- if (mask.getMasks().get(i).getMaskMode() == maskMode) {
- hasMask = true;
- break;
- }
- }
- if (!hasMask) {
- return;
- }
-
- L.beginSection("Layer#drawMask");
L.beginSection("Layer#saveLayer");
- saveLayerCompat(canvas, rect, paint, false);
+ saveLayerCompat(canvas, rect, dstInPaint, false);
+// canvas.drawRect(rect.left, rect.top, rect.right, rect.bottom, subtractMaskStartingPaintNormal);
L.endSection("Layer#saveLayer");
- clearCanvas(canvas);
-
- for (int i = 0; i < size; i++) {
+ for (int i = 0; i < mask.getMasks().size(); i++) {
Mask mask = this.mask.getMasks().get(i);
- if (mask.getMaskMode() != maskMode) {
- continue;
+ BaseKeyframeAnimation<ShapeData, Path> maskAnimation = this.mask.getMaskAnimations().get(i);
+ BaseKeyframeAnimation<Integer, Integer> opacityAnimation = this.mask.getOpacityAnimations().get(i);
+ switch (mask.getMaskMode()) {
+ case MASK_MODE_ADD:
+ if (mask.isInverted()) {
+ applyInvertedAddMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
+ } else {
+ applyAddMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
+ }
+ break;
+ case MASK_MODE_SUBTRACT:
+ if (i == 0) {
+ // TODO: make a paint for this.
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+ canvas.drawRect(rect, paint);
+ }
+ if (mask.isInverted()) {
+ applyInvertedSubtractMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
+ } else {
+ applySubtractMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
+ }
+ break;
+ case MASK_MODE_INTERSECT:
+ applyIntersectMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
+ break;
}
- BaseKeyframeAnimation<?, Path> maskAnimation = this.mask.getMaskAnimations().get(i);
- Path maskPath = maskAnimation.getValue();
- path.set(maskPath);
- path.transform(matrix);
- BaseKeyframeAnimation<Integer, Integer> opacityAnimation =
- this.mask.getOpacityAnimations().get(i);
- int alpha = contentPaint.getAlpha();
- contentPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
- canvas.drawPath(path, contentPaint);
- contentPaint.setAlpha(alpha);
}
L.beginSection("Layer#restoreLayer");
canvas.restore();
L.endSection("Layer#restoreLayer");
- L.endSection("Layer#drawMask");
+ }
+
+ private void applyAddMask(Canvas canvas, Matrix matrix, Mask mask,
+ BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+ Path maskPath = maskAnimation.getValue();
+ path.set(maskPath);
+ path.transform(matrix);
+ addMaskPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
+ canvas.drawPath(path, addMaskPaint);
+ }
+
+ private void applyInvertedAddMask(Canvas canvas, Matrix matrix, Mask mask,
+ BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+ saveLayerCompat(canvas, rect, addInvMaskLayerPaint, true);
+ canvas.drawRect(rect, addInvMaskLayerPaint);
+ Path maskPath = maskAnimation.getValue();
+ path.set(maskPath);
+ path.transform(matrix);
+ addMaskPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
+ canvas.drawPath(path, addInvMaskPaintClear);
+ canvas.restore();
+ }
+
+ private void applySubtractMask(Canvas canvas, Matrix matrix, Mask mask,
+ BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+ Path maskPath = maskAnimation.getValue();
+ path.set(maskPath);
+ path.transform(matrix);
+ canvas.drawPath(path, subtractMaskPaintDstOut);
+ }
+
+ private void applyInvertedSubtractMask(Canvas canvas, Matrix matrix, Mask mask,
+ BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+ saveLayerCompat(canvas, rect, subtractMaskPaintDstOut, true);
+ canvas.drawRect(rect, subtractMaskStartingPaintNormal);
+ subtractMaskPaintDstOut.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
+ Path maskPath = maskAnimation.getValue();
+ path.set(maskPath);
+ path.transform(matrix);
+ canvas.drawPath(path, subtractMaskPaintDstOut);
+ canvas.restore();
+ }
+
+ private void applyIntersectMask(Canvas canvas, Matrix matrix, Mask mask,
+ BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+ saveLayerCompat(canvas, rect, dstInPaint, true);
+ Path maskPath = maskAnimation.getValue();
+ path.set(maskPath);
+ path.transform(matrix);
+ contentPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
+ canvas.drawPath(path, contentPaint);
+ canvas.restore();
}
boolean hasMasksOnThisLayer() {
@@ -469,15 +509,18 @@
}
}
- @Override public String getName() {
+ @Override
+ public String getName() {
return layerModel.getName();
}
- @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
+ @Override
+ public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
// Do nothing
}
- @Override public void resolveKeyPath(
+ @Override
+ public void resolveKeyPath(
KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
if (!keyPath.matches(getName(), depth)) {
return;
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/MaskParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/MaskParser.java
index 32e77de..a25300e 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/MaskParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/MaskParser.java
@@ -20,6 +20,7 @@
Mask.MaskMode maskMode = null;
AnimatableShapeValue maskPath = null;
AnimatableIntegerValue opacity = null;
+ boolean inverted = false;
reader.beginObject();
while (reader.hasNext()) {
@@ -49,13 +50,16 @@
case "o":
opacity = AnimatableValueParser.parseInteger(reader, composition);
break;
+ case "inv":
+ inverted = reader.nextBoolean();
+ break;
default:
reader.skipValue();
}
}
reader.endObject();
- return new Mask(maskMode, maskPath, opacity);
+ return new Mask(maskMode, maskPath, opacity, inverted);
}
}