Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(EraserBrush): inverted erasing #7689

Merged
merged 6 commits into from
Mar 28, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 44 additions & 42 deletions src/mixins/eraser_brush.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,7 @@
*/
_renderOverlay: function (ctx) {
__renderOverlay.call(this, ctx);
if (this.isErasing() && !this.freeDrawingBrush.inverted) {
this.freeDrawingBrush._render();
}
this.isErasing() && this.freeDrawingBrush._render();
}
});

Expand Down Expand Up @@ -357,12 +355,12 @@

/**
* @private
* This is designed to support erasing a collection with both erasable and non-erasable objects.
* Iterates over collections to allow nested selective erasing.
* Prepares the pattern brush that will draw on the top context to achieve the desired visual effect.
* If brush is **NOT** inverted render all non-erasable objects.
* If brush is inverted render all erasable objects that have been erased with their clip path inverted.
* This will render the erased parts as if they were not erased.
* This is designed to support erasing a collection with both erasable and non-erasable objects while maintaining object stacking.\
* Iterates over collections to allow nested selective erasing.\
* Prepares objects before rendering the pattern brush.\
* If brush is **NOT** inverted render all non-erasable objects.\
* If brush is inverted render all objects, erasable objects without their eraser.
* This will render the erased parts as if they were not erased in the first place, achieving an undo effect.
*
* @param {fabric.Collection} collection
* @param {CanvasRenderingContext2D} ctx
Expand All @@ -381,29 +379,22 @@
restorationContext.visibility.push(obj);
restorationContext.collection.push(collection);
}
else if (this.inverted && obj.visible) {
// render only erasable objects that were erased
if (obj.erasable && obj.eraser) {
obj.eraser.inverted = true;
obj.dirty = true;
collection.dirty = true;
restorationContext.eraser.push(obj);
restorationContext.collection.push(collection);
}
else {
obj.visible = false;
collection.dirty = true;
restorationContext.visibility.push(obj);
restorationContext.collection.push(collection);
}
else if (this.inverted && obj.erasable && obj.eraser && obj.visible) {
// render all objects without eraser
var eraser = obj.eraser;
obj.eraser = undefined;
obj.dirty = true;
collection.dirty = true;
restorationContext.eraser.push([obj, eraser]);
restorationContext.collection.push(collection);
}
}, this);
},

/**
* Prepare the pattern for the erasing brush
* This pattern will be drawn on the top context, achieving a visual effect of erasing only erasable objects
* @todo decide how overlay color should behave when `inverted === true`, currently draws over it which is undesirable
* This pattern will be drawn on the top context after clipping the main context,
* achieving a visual effect of erasing only erasable objects
* @private
*/
preparePattern: function () {
Expand All @@ -427,20 +418,27 @@
this.canvas._renderBackground(patternCtx);
if (bgErasable) { this.canvas.backgroundImage = backgroundImage; }
}
else if (this.inverted && (backgroundImage && bgErasable)) {
var color = this.canvas.backgroundColor;
this.canvas.backgroundColor = undefined;
else if (this.inverted) {
var eraser = backgroundImage && backgroundImage.eraser;
if (eraser) {
backgroundImage.eraser = undefined;
backgroundImage.dirty = true;
}
this.canvas._renderBackground(patternCtx);
this.canvas.backgroundColor = color;
if (eraser) {
backgroundImage.eraser = eraser;
backgroundImage.dirty = true;
}
}
patternCtx.save();
patternCtx.transform.apply(patternCtx, this.canvas.viewportTransform);
var restorationContext = { visibility: [], eraser: [], collection: [] };
this._prepareCollectionTraversal(this.canvas, patternCtx, restorationContext);
this.canvas._renderObjects(patternCtx, this.canvas._objects);
restorationContext.visibility.forEach(function (obj) { obj.visible = true; });
restorationContext.eraser.forEach(function (obj) {
obj.eraser.inverted = false;
restorationContext.eraser.forEach(function (entry) {
var obj = entry[0], eraser = entry[1];
obj.eraser = eraser;
obj.dirty = true;
});
restorationContext.collection.forEach(function (obj) { obj.dirty = true; });
Expand All @@ -450,11 +448,17 @@
__renderOverlay.call(this.canvas, patternCtx);
if (overlayErasable) { this.canvas.overlayImage = overlayImage; }
}
else if (this.inverted && (overlayImage && overlayErasable)) {
var color = this.canvas.overlayColor;
this.canvas.overlayColor = undefined;
else if (this.inverted) {
var eraser = overlayImage && overlayImage.eraser;
if (eraser) {
overlayImage.eraser = undefined;
overlayImage.dirty = true;
}
__renderOverlay.call(this.canvas, patternCtx);
this.canvas.overlayColor = color;
if (eraser) {
overlayImage.eraser = eraser;
overlayImage.dirty = true;
}
}
},

Expand Down Expand Up @@ -526,12 +530,10 @@
*/
_render: function () {
var ctx;
if (!this.inverted) {
// clip canvas
ctx = this.canvas.getContext();
this.callSuper('_render', ctx);
}
// render brush and mask it with image of non erasables
// clip canvas
ctx = this.canvas.getContext();
this.callSuper('_render', ctx);
// render brush and mask it with pattern
ctx = this.canvas.contextTop;
this.canvas.clearContext(ctx);
this.callSuper('_render', ctx);
Expand Down