|
7 | 7 | BACKGROUND_RECT_ID,
|
8 | 8 | CA_LAYER_IMAGE_NAME,
|
9 | 9 | CA_LAYER_NAME,
|
| 10 | + COMPOSITING_RECT_NAME, |
10 | 11 | getCALayerImageId,
|
11 | 12 | getIILayerImageId,
|
12 | 13 | getLayerBboxId,
|
@@ -324,6 +325,12 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
|
324 | 325 | return vectorMaskRect;
|
325 | 326 | };
|
326 | 327 |
|
| 328 | +const createCompositingRect = (konvaLayer: Konva.Layer): Konva.Rect => { |
| 329 | + const compositingRect = new Konva.Rect({ name: COMPOSITING_RECT_NAME, listening: false }); |
| 330 | + konvaLayer.add(compositingRect); |
| 331 | + return compositingRect; |
| 332 | +}; |
| 333 | + |
327 | 334 | /**
|
328 | 335 | * Renders a vector mask layer.
|
329 | 336 | * @param stage The konva stage to render on.
|
@@ -401,15 +408,53 @@ const renderRegionalGuidanceLayer = (
|
401 | 408 | groupNeedsCache = true;
|
402 | 409 | }
|
403 | 410 |
|
404 |
| - if (konvaObjectGroup.children.length === 0) { |
| 411 | + if (konvaObjectGroup.getChildren().length === 0) { |
405 | 412 | // No objects - clear the cache to reset the previous pixel data
|
406 | 413 | konvaObjectGroup.clearCache();
|
407 |
| - } else if (groupNeedsCache) { |
408 |
| - konvaObjectGroup.cache(); |
| 414 | + return; |
409 | 415 | }
|
410 | 416 |
|
411 |
| - // Updating group opacity does not require re-caching |
412 |
| - if (konvaObjectGroup.opacity() !== globalMaskLayerOpacity) { |
| 417 | + const compositingRect = |
| 418 | + konvaLayer.findOne<Konva.Rect>(`.${COMPOSITING_RECT_NAME}`) ?? createCompositingRect(konvaLayer); |
| 419 | + |
| 420 | + /** |
| 421 | + * When the group is selected, we use a rect of the selected preview color, composited over the shapes. This allows |
| 422 | + * shapes to render as a "raster" layer with all pixels drawn at the same color and opacity. |
| 423 | + * |
| 424 | + * Without this special handling, each shape is drawn individually with the given opacity, atop the other shapes. The |
| 425 | + * effect is like if you have a Photoshop Group consisting of many shapes, each of which has the given opacity. |
| 426 | + * Overlapping shapes will have their colors blended together, and the final color is the result of all the shapes. |
| 427 | + * |
| 428 | + * Instead, with the special handling, the effect is as if you drew all the shapes at 100% opacity, flattened them to |
| 429 | + * a single raster image, and _then_ applied the 50% opacity. |
| 430 | + */ |
| 431 | + if (reduxLayer.isSelected && tool !== 'move') { |
| 432 | + // We must clear the cache first so Konva will re-draw the group with the new compositing rect |
| 433 | + if (konvaObjectGroup.isCached()) { |
| 434 | + konvaObjectGroup.clearCache(); |
| 435 | + } |
| 436 | + // The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work |
| 437 | + konvaObjectGroup.opacity(1); |
| 438 | + |
| 439 | + compositingRect.setAttrs({ |
| 440 | + // The rect should be the size of the layer - use the fast method bc it's OK if the rect is larger |
| 441 | + ...getLayerBboxFast(konvaLayer), |
| 442 | + fill: rgbColor, |
| 443 | + opacity: globalMaskLayerOpacity, |
| 444 | + // Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes) |
| 445 | + globalCompositeOperation: 'source-in', |
| 446 | + visible: true, |
| 447 | + // This rect must always be on top of all other shapes |
| 448 | + zIndex: konvaObjectGroup.getChildren().length, |
| 449 | + }); |
| 450 | + } else { |
| 451 | + // The compositing rect should only be shown when the layer is selected. |
| 452 | + compositingRect.visible(false); |
| 453 | + // Cache only if needed - or if we are on this code path and _don't_ have a cache |
| 454 | + if (groupNeedsCache || !konvaObjectGroup.isCached()) { |
| 455 | + konvaObjectGroup.cache(); |
| 456 | + } |
| 457 | + // Updating group opacity does not require re-caching |
413 | 458 | konvaObjectGroup.opacity(globalMaskLayerOpacity);
|
414 | 459 | }
|
415 | 460 | };
|
|
0 commit comments