Merge pull request #27 from outis1one/claude/migrate-to-minipaint-eYKWf
Improve SAM selection with mask contour and layer operations
This commit is contained in:
@@ -61,20 +61,6 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if eye catalog needs to be populated
|
|
||||||
echo ""
|
|
||||||
echo "Checking eye catalog..."
|
|
||||||
echo "------------------------------------------"
|
|
||||||
PATCHES_COUNT=$(find /app/data/patches -maxdepth 1 -type d 2>/dev/null | wc -l)
|
|
||||||
DB_PATCHES_COUNT=$(sqlite3 /app/data/ai_photo_edit.db "SELECT COUNT(*) FROM patches;" 2>/dev/null || echo "0")
|
|
||||||
|
|
||||||
if [ "$DB_PATCHES_COUNT" = "0" ] || [ "$PATCHES_COUNT" -le 1 ]; then
|
|
||||||
echo "Eye catalog is empty. Downloading sample eyes..."
|
|
||||||
cd /app && python /scripts/download_sample_eyes.py || echo "Warning: Could not download sample eyes (non-fatal)"
|
|
||||||
else
|
|
||||||
echo "✓ Eye catalog has $DB_PATCHES_COUNT patches"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "Starting FastAPI server..."
|
echo "Starting FastAPI server..."
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Smart Select Tool - Uses SAM (Segment Anything Model) for AI-powered selection
|
* Smart Select Tool - Uses SAM (Segment Anything Model) for AI-powered selection
|
||||||
* Click on any object to automatically select it
|
* Click on any object to automatically select it
|
||||||
|
* Supports: Copy to layer, Cut to layer, Delete selection, AI Inpaint
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import app from './../app.js';
|
import app from './../app.js';
|
||||||
@@ -27,6 +28,13 @@ class Smart_select_class extends Base_tools_class {
|
|||||||
this.maskCanvas = null;
|
this.maskCanvas = null;
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
this.selectionBounds = null;
|
this.selectionBounds = null;
|
||||||
|
|
||||||
|
// Marching ants animation
|
||||||
|
this.marchingAntsOffset = 0;
|
||||||
|
this.animationFrame = null;
|
||||||
|
|
||||||
|
// Contour path for drawing the mask outline
|
||||||
|
this.contourPath = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
@@ -36,6 +44,64 @@ class Smart_select_class extends Base_tools_class {
|
|||||||
document.addEventListener('mousedown', function (e) {
|
document.addEventListener('mousedown', function (e) {
|
||||||
_this.mousedown(e);
|
_this.mousedown(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keyboard shortcuts
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (config.TOOL.name != _this.name) return;
|
||||||
|
if (_this.Helper.is_input(e.target)) return;
|
||||||
|
|
||||||
|
var code = e.keyCode;
|
||||||
|
|
||||||
|
// Delete - delete selected area
|
||||||
|
if (code == 46 && _this.currentMask) {
|
||||||
|
e.preventDefault();
|
||||||
|
_this.deleteSelection();
|
||||||
|
}
|
||||||
|
// Escape - clear selection
|
||||||
|
if (code == 27 && _this.currentMask) {
|
||||||
|
e.preventDefault();
|
||||||
|
_this.clearSelection();
|
||||||
|
}
|
||||||
|
// Ctrl+C - copy to new layer
|
||||||
|
if (code == 67 && (e.ctrlKey || e.metaKey) && _this.currentMask) {
|
||||||
|
e.preventDefault();
|
||||||
|
_this.copyToLayer();
|
||||||
|
}
|
||||||
|
// Ctrl+X - cut to new layer
|
||||||
|
if (code == 88 && (e.ctrlKey || e.metaKey) && _this.currentMask) {
|
||||||
|
e.preventDefault();
|
||||||
|
_this.cutToLayer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start marching ants animation
|
||||||
|
this.startMarchingAnts();
|
||||||
|
}
|
||||||
|
|
||||||
|
startMarchingAnts() {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
var animate = function() {
|
||||||
|
_this.marchingAntsOffset++;
|
||||||
|
if (_this.marchingAntsOffset > 16) {
|
||||||
|
_this.marchingAntsOffset = 0;
|
||||||
|
}
|
||||||
|
if (_this.currentMask) {
|
||||||
|
config.need_render = true;
|
||||||
|
}
|
||||||
|
_this.animationFrame = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Slower animation - every 100ms
|
||||||
|
setInterval(function() {
|
||||||
|
if (_this.currentMask) {
|
||||||
|
_this.marchingAntsOffset++;
|
||||||
|
if (_this.marchingAntsOffset > 16) {
|
||||||
|
_this.marchingAntsOffset = 0;
|
||||||
|
}
|
||||||
|
config.need_render = true;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
async mousedown(e) {
|
async mousedown(e) {
|
||||||
@@ -85,7 +151,7 @@ class Smart_select_class extends Base_tools_class {
|
|||||||
// Apply the mask as selection
|
// Apply the mask as selection
|
||||||
this.applyMask(result.mask, result.bbox);
|
this.applyMask(result.mask, result.bbox);
|
||||||
|
|
||||||
alertify.success('Selection complete! Use AI Inpaint to edit.');
|
alertify.success('Selection complete! Use Ctrl+C to copy, Ctrl+X to cut, Delete to remove, or AI Inpaint to edit.');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Smart select error:', error);
|
console.error('Smart select error:', error);
|
||||||
@@ -141,6 +207,9 @@ class Smart_select_class extends Base_tools_class {
|
|||||||
// Calculate selection bounds from mask
|
// Calculate selection bounds from mask
|
||||||
_this.calculateSelectionBounds();
|
_this.calculateSelectionBounds();
|
||||||
|
|
||||||
|
// Extract contour path from the mask
|
||||||
|
_this.extractContourPath();
|
||||||
|
|
||||||
// Trigger re-render
|
// Trigger re-render
|
||||||
config.need_render = true;
|
config.need_render = true;
|
||||||
_this.Base_layers.render();
|
_this.Base_layers.render();
|
||||||
@@ -184,35 +253,93 @@ class Smart_select_class extends Base_tools_class {
|
|||||||
x: config.layer.x + minX * scaleX,
|
x: config.layer.x + minX * scaleX,
|
||||||
y: config.layer.y + minY * scaleY,
|
y: config.layer.y + minY * scaleY,
|
||||||
width: (maxX - minX) * scaleX,
|
width: (maxX - minX) * scaleX,
|
||||||
height: (maxY - minY) * scaleY
|
height: (maxY - minY) * scaleY,
|
||||||
|
// Store original coordinates too
|
||||||
|
origMinX: minX,
|
||||||
|
origMinY: minY,
|
||||||
|
origMaxX: maxX,
|
||||||
|
origMaxY: maxY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract contour points from the mask for drawing the outline
|
||||||
|
* Uses a simple edge detection approach
|
||||||
|
*/
|
||||||
|
extractContourPath() {
|
||||||
|
if (!this.maskCanvas) return;
|
||||||
|
|
||||||
|
var maskCtx = this.maskCanvas.getContext('2d');
|
||||||
|
var imageData = maskCtx.getImageData(0, 0, this.maskCanvas.width, this.maskCanvas.height);
|
||||||
|
var width = this.maskCanvas.width;
|
||||||
|
var height = this.maskCanvas.height;
|
||||||
|
var data = imageData.data;
|
||||||
|
|
||||||
|
// Create edge canvas - pixels that are on the edge of the mask
|
||||||
|
this.edgeCanvas = document.createElement('canvas');
|
||||||
|
this.edgeCanvas.width = width;
|
||||||
|
this.edgeCanvas.height = height;
|
||||||
|
var edgeCtx = this.edgeCanvas.getContext('2d');
|
||||||
|
var edgeImageData = edgeCtx.createImageData(width, height);
|
||||||
|
var edgeData = edgeImageData.data;
|
||||||
|
|
||||||
|
// Find edge pixels (mask pixels adjacent to non-mask pixels)
|
||||||
|
for (var y = 0; y < height; y++) {
|
||||||
|
for (var x = 0; x < width; x++) {
|
||||||
|
var i = (y * width + x) * 4;
|
||||||
|
var isMask = data[i] > 128;
|
||||||
|
|
||||||
|
if (isMask) {
|
||||||
|
// Check if any neighbor is NOT mask (edge pixel)
|
||||||
|
var isEdge = false;
|
||||||
|
|
||||||
|
// Check 4-connected neighbors
|
||||||
|
if (x > 0 && data[i - 4] <= 128) isEdge = true;
|
||||||
|
if (x < width - 1 && data[i + 4] <= 128) isEdge = true;
|
||||||
|
if (y > 0 && data[i - width * 4] <= 128) isEdge = true;
|
||||||
|
if (y < height - 1 && data[i + width * 4] <= 128) isEdge = true;
|
||||||
|
|
||||||
|
// Also check boundary
|
||||||
|
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) isEdge = true;
|
||||||
|
|
||||||
|
if (isEdge) {
|
||||||
|
edgeData[i] = 255;
|
||||||
|
edgeData[i + 1] = 255;
|
||||||
|
edgeData[i + 2] = 255;
|
||||||
|
edgeData[i + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
edgeCtx.putImageData(edgeImageData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render overlay - called by miniPaint's rendering system
|
* Render overlay - called by miniPaint's rendering system
|
||||||
|
* Shows the mask with marching ants outline
|
||||||
*/
|
*/
|
||||||
render_overlay(ctx) {
|
render_overlay(ctx) {
|
||||||
if (!this.currentMask || !this.maskCanvas) return;
|
if (!this.currentMask || !this.maskCanvas) return;
|
||||||
|
|
||||||
// Draw semi-transparent overlay on non-selected areas
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
// Scale to match layer
|
// Scale to match layer
|
||||||
var scaleX = config.layer.width / config.layer.width_original;
|
var scaleX = config.layer.width / config.layer.width_original;
|
||||||
var scaleY = config.layer.height / config.layer.height_original;
|
var scaleY = config.layer.height / config.layer.height_original;
|
||||||
|
|
||||||
// Create inverse mask canvas (darkens unselected areas)
|
// Draw semi-transparent overlay on non-selected areas
|
||||||
var inverseCanvas = document.createElement('canvas');
|
var inverseCanvas = document.createElement('canvas');
|
||||||
inverseCanvas.width = this.maskCanvas.width;
|
inverseCanvas.width = this.maskCanvas.width;
|
||||||
inverseCanvas.height = this.maskCanvas.height;
|
inverseCanvas.height = this.maskCanvas.height;
|
||||||
var inverseCtx = inverseCanvas.getContext('2d');
|
var inverseCtx = inverseCanvas.getContext('2d');
|
||||||
|
|
||||||
// Fill with semi-transparent black
|
// Fill with semi-transparent black
|
||||||
inverseCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
inverseCtx.fillStyle = 'rgba(0, 0, 0, 0.4)';
|
||||||
inverseCtx.fillRect(0, 0, inverseCanvas.width, inverseCanvas.height);
|
inverseCtx.fillRect(0, 0, inverseCanvas.width, inverseCanvas.height);
|
||||||
|
|
||||||
// Cut out the selected area
|
// Cut out the selected area (so selected area is NOT darkened)
|
||||||
inverseCtx.globalCompositeOperation = 'destination-out';
|
inverseCtx.globalCompositeOperation = 'destination-out';
|
||||||
inverseCtx.drawImage(this.maskCanvas, 0, 0);
|
inverseCtx.drawImage(this.maskCanvas, 0, 0);
|
||||||
|
|
||||||
@@ -223,31 +350,283 @@ class Smart_select_class extends Base_tools_class {
|
|||||||
config.layer.width, config.layer.height
|
config.layer.width, config.layer.height
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw marching ants border around selection
|
// Draw marching ants border around the actual mask contour
|
||||||
if (this.selectionBounds) {
|
if (this.edgeCanvas) {
|
||||||
ctx.strokeStyle = '#00ff00';
|
// Create a canvas for marching ants effect
|
||||||
ctx.lineWidth = 2;
|
var antsCanvas = document.createElement('canvas');
|
||||||
ctx.setLineDash([5, 5]);
|
antsCanvas.width = this.maskCanvas.width;
|
||||||
ctx.strokeRect(
|
antsCanvas.height = this.maskCanvas.height;
|
||||||
this.selectionBounds.x,
|
var antsCtx = antsCanvas.getContext('2d');
|
||||||
this.selectionBounds.y,
|
|
||||||
this.selectionBounds.width,
|
// Draw the edge in white (visible part of marching ants)
|
||||||
this.selectionBounds.height
|
antsCtx.drawImage(this.edgeCanvas, 0, 0);
|
||||||
|
|
||||||
|
// Apply marching ants pattern using composite
|
||||||
|
antsCtx.globalCompositeOperation = 'source-in';
|
||||||
|
|
||||||
|
// Create marching ants pattern (alternating black and white)
|
||||||
|
var pattern = antsCtx.createLinearGradient(0, 0, 16, 16);
|
||||||
|
var offset = this.marchingAntsOffset / 16;
|
||||||
|
|
||||||
|
// Create dashed pattern
|
||||||
|
for (var i = 0; i < 2; i++) {
|
||||||
|
var pos1 = ((i * 0.5) + offset) % 1;
|
||||||
|
var pos2 = ((i * 0.5 + 0.25) + offset) % 1;
|
||||||
|
|
||||||
|
if (pos1 < pos2) {
|
||||||
|
pattern.addColorStop(pos1, '#00ff00');
|
||||||
|
pattern.addColorStop(pos2, '#00ff00');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
antsCtx.fillStyle = '#00ff00';
|
||||||
|
antsCtx.fillRect(0, 0, antsCanvas.width, antsCanvas.height);
|
||||||
|
|
||||||
|
// Draw the marching ants outline
|
||||||
|
ctx.drawImage(
|
||||||
|
antsCanvas,
|
||||||
|
config.layer.x, config.layer.y,
|
||||||
|
config.layer.width, config.layer.height
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Also draw a second pass with offset for alternating colors
|
||||||
|
var antsCanvas2 = document.createElement('canvas');
|
||||||
|
antsCanvas2.width = this.maskCanvas.width;
|
||||||
|
antsCanvas2.height = this.maskCanvas.height;
|
||||||
|
var antsCtx2 = antsCanvas2.getContext('2d');
|
||||||
|
|
||||||
|
// Dilate the edge slightly for the black outline
|
||||||
|
antsCtx2.drawImage(this.edgeCanvas, 0, 0);
|
||||||
|
antsCtx2.globalCompositeOperation = 'source-in';
|
||||||
|
|
||||||
|
// Alternate pattern
|
||||||
|
antsCtx2.fillStyle = ((Math.floor(this.marchingAntsOffset / 4) % 2) === 0) ? '#ffffff' : '#000000';
|
||||||
|
antsCtx2.fillRect(0, 0, antsCanvas2.width, antsCanvas2.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy selected area to a new layer
|
||||||
|
*/
|
||||||
|
copyToLayer() {
|
||||||
|
if (!this.currentMask || !this.maskCanvas) {
|
||||||
|
alertify.error('No selection to copy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer = config.layer;
|
||||||
|
if (layer.type != 'image') {
|
||||||
|
alertify.error('Layer must be an image');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create canvas with just the selected pixels
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = layer.width_original;
|
||||||
|
canvas.height = layer.height_original;
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Draw original image
|
||||||
|
ctx.drawImage(layer.link, 0, 0);
|
||||||
|
|
||||||
|
// Apply mask - keep only selected pixels
|
||||||
|
ctx.globalCompositeOperation = 'destination-in';
|
||||||
|
ctx.drawImage(this.maskCanvas, 0, 0);
|
||||||
|
|
||||||
|
// Get the bounds of the selection to crop the canvas
|
||||||
|
var bounds = this.selectionBounds;
|
||||||
|
if (!bounds) {
|
||||||
|
alertify.error('Invalid selection bounds');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop to selection bounds
|
||||||
|
var croppedCanvas = document.createElement('canvas');
|
||||||
|
var cropWidth = bounds.origMaxX - bounds.origMinX;
|
||||||
|
var cropHeight = bounds.origMaxY - bounds.origMinY;
|
||||||
|
croppedCanvas.width = cropWidth;
|
||||||
|
croppedCanvas.height = cropHeight;
|
||||||
|
var croppedCtx = croppedCanvas.getContext('2d');
|
||||||
|
|
||||||
|
croppedCtx.drawImage(
|
||||||
|
canvas,
|
||||||
|
bounds.origMinX, bounds.origMinY, cropWidth, cropHeight,
|
||||||
|
0, 0, cropWidth, cropHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate position for new layer
|
||||||
|
var scaleX = layer.width / layer.width_original;
|
||||||
|
var scaleY = layer.height / layer.height_original;
|
||||||
|
|
||||||
|
// Create new layer with the selection
|
||||||
|
var params = {
|
||||||
|
x: Math.round(layer.x + bounds.origMinX * scaleX),
|
||||||
|
y: Math.round(layer.y + bounds.origMinY * scaleY),
|
||||||
|
width: Math.round(cropWidth * scaleX),
|
||||||
|
height: Math.round(cropHeight * scaleY),
|
||||||
|
width_original: cropWidth,
|
||||||
|
height_original: cropHeight,
|
||||||
|
type: 'image',
|
||||||
|
name: 'AI Selection Copy',
|
||||||
|
data: croppedCanvas.toDataURL('image/png')
|
||||||
|
};
|
||||||
|
|
||||||
|
app.State.do_action(
|
||||||
|
new app.Actions.Bundle_action('copy_selection_to_layer', 'Copy Selection to Layer', [
|
||||||
|
new app.Actions.Insert_layer_action(params, false)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
alertify.success('Selection copied to new layer!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cut selected area to a new layer (copy + delete from original)
|
||||||
|
*/
|
||||||
|
cutToLayer() {
|
||||||
|
if (!this.currentMask || !this.maskCanvas) {
|
||||||
|
alertify.error('No selection to cut');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer = config.layer;
|
||||||
|
if (layer.type != 'image') {
|
||||||
|
alertify.error('Layer must be an image');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First copy to new layer
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = layer.width_original;
|
||||||
|
canvas.height = layer.height_original;
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Draw original image
|
||||||
|
ctx.drawImage(layer.link, 0, 0);
|
||||||
|
|
||||||
|
// Apply mask - keep only selected pixels
|
||||||
|
ctx.globalCompositeOperation = 'destination-in';
|
||||||
|
ctx.drawImage(this.maskCanvas, 0, 0);
|
||||||
|
|
||||||
|
// Get the bounds of the selection
|
||||||
|
var bounds = this.selectionBounds;
|
||||||
|
if (!bounds) {
|
||||||
|
alertify.error('Invalid selection bounds');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop to selection bounds
|
||||||
|
var croppedCanvas = document.createElement('canvas');
|
||||||
|
var cropWidth = bounds.origMaxX - bounds.origMinX;
|
||||||
|
var cropHeight = bounds.origMaxY - bounds.origMinY;
|
||||||
|
croppedCanvas.width = cropWidth;
|
||||||
|
croppedCanvas.height = cropHeight;
|
||||||
|
var croppedCtx = croppedCanvas.getContext('2d');
|
||||||
|
|
||||||
|
croppedCtx.drawImage(
|
||||||
|
canvas,
|
||||||
|
bounds.origMinX, bounds.origMinY, cropWidth, cropHeight,
|
||||||
|
0, 0, cropWidth, cropHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate position for new layer
|
||||||
|
var scaleX = layer.width / layer.width_original;
|
||||||
|
var scaleY = layer.height / layer.height_original;
|
||||||
|
|
||||||
|
// Create params for new layer
|
||||||
|
var params = {
|
||||||
|
x: Math.round(layer.x + bounds.origMinX * scaleX),
|
||||||
|
y: Math.round(layer.y + bounds.origMinY * scaleY),
|
||||||
|
width: Math.round(cropWidth * scaleX),
|
||||||
|
height: Math.round(cropHeight * scaleY),
|
||||||
|
width_original: cropWidth,
|
||||||
|
height_original: cropHeight,
|
||||||
|
type: 'image',
|
||||||
|
name: 'AI Selection Cut',
|
||||||
|
data: croppedCanvas.toDataURL('image/png')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now delete from original - create canvas with hole
|
||||||
|
var holeCanvas = document.createElement('canvas');
|
||||||
|
holeCanvas.width = layer.width_original;
|
||||||
|
holeCanvas.height = layer.height_original;
|
||||||
|
var holeCtx = holeCanvas.getContext('2d');
|
||||||
|
|
||||||
|
// Draw original image
|
||||||
|
holeCtx.drawImage(layer.link, 0, 0);
|
||||||
|
|
||||||
|
// Cut out the mask area
|
||||||
|
holeCtx.globalCompositeOperation = 'destination-out';
|
||||||
|
holeCtx.drawImage(this.maskCanvas, 0, 0);
|
||||||
|
|
||||||
|
// Execute both actions
|
||||||
|
app.State.do_action(
|
||||||
|
new app.Actions.Bundle_action('cut_selection_to_layer', 'Cut Selection to Layer', [
|
||||||
|
new app.Actions.Update_layer_image_action(holeCanvas),
|
||||||
|
new app.Actions.Insert_layer_action(params, false)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear the selection
|
||||||
|
this.clearSelection();
|
||||||
|
|
||||||
|
alertify.success('Selection cut to new layer!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the selected area from the image
|
||||||
|
*/
|
||||||
|
deleteSelection() {
|
||||||
|
if (!this.currentMask || !this.maskCanvas) {
|
||||||
|
alertify.error('No selection to delete');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer = config.layer;
|
||||||
|
if (layer.type != 'image') {
|
||||||
|
alertify.error('Layer must be an image');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create canvas with hole where selection was
|
||||||
|
var holeCanvas = document.createElement('canvas');
|
||||||
|
holeCanvas.width = layer.width_original;
|
||||||
|
holeCanvas.height = layer.height_original;
|
||||||
|
var holeCtx = holeCanvas.getContext('2d');
|
||||||
|
|
||||||
|
// Draw original image
|
||||||
|
holeCtx.drawImage(layer.link, 0, 0);
|
||||||
|
|
||||||
|
// Cut out the mask area
|
||||||
|
holeCtx.globalCompositeOperation = 'destination-out';
|
||||||
|
holeCtx.drawImage(this.maskCanvas, 0, 0);
|
||||||
|
|
||||||
|
app.State.do_action(
|
||||||
|
new app.Actions.Bundle_action('delete_selection', 'Delete Selection', [
|
||||||
|
new app.Actions.Update_layer_image_action(holeCanvas)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear the selection
|
||||||
|
this.clearSelection();
|
||||||
|
|
||||||
|
alertify.success('Selection deleted!');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the current selection
|
* Clear the current selection
|
||||||
*/
|
*/
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.currentMask = null;
|
this.currentMask = null;
|
||||||
this.maskCanvas = null;
|
this.maskCanvas = null;
|
||||||
|
this.edgeCanvas = null;
|
||||||
this.selectionBounds = null;
|
this.selectionBounds = null;
|
||||||
|
this.contourPath = null;
|
||||||
window.smartSelectMask = null;
|
window.smartSelectMask = null;
|
||||||
config.need_render = true;
|
config.need_render = true;
|
||||||
|
this.Base_layers.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
on_leave() {
|
on_leave() {
|
||||||
|
|||||||
Reference in New Issue
Block a user