Merge pull request #26 from outis1one/claude/migrate-to-minipaint-eYKWf

Fix smart select visualization and inpaint API
This commit is contained in:
outis1one
2026-01-26 13:27:52 -05:00
committed by GitHub
2 changed files with 57 additions and 43 deletions
+9 -11
View File
@@ -89,28 +89,26 @@ async def inpaint_base64(request: InpaintRequest):
if mask_img.size != img.size: if mask_img.size != img.size:
mask_img = mask_img.resize(img.size, Image.Resampling.LANCZOS) mask_img = mask_img.resize(img.size, Image.Resampling.LANCZOS)
# Get the AI provider and run inpainting # Get the AI provider
from app.services.ai_provider import get_ai_provider from app.services.ai_provider import get_ai_provider
from app.config import settings
provider = get_ai_provider() provider = get_ai_provider()
# Convert images to format expected by provider # Convert images to bytes for provider
img_buffer = BytesIO() img_buffer = BytesIO()
img.save(img_buffer, format='PNG') img.save(img_buffer, format='PNG')
img_buffer.seek(0) img_bytes = img_buffer.getvalue()
mask_buffer = BytesIO() mask_buffer = BytesIO()
mask_img.save(mask_buffer, format='PNG') mask_img.save(mask_buffer, format='PNG')
mask_buffer.seek(0) mask_bytes_png = mask_buffer.getvalue()
# Run inpainting # Run inpainting using edit_image method
result_bytes = await provider.inpaint( result_bytes = await provider.edit_image(
image=img_buffer, patch_image_bytes=img_bytes,
mask=mask_buffer, mask_image_bytes=mask_bytes_png,
prompt=request.prompt, prompt=request.prompt,
negative_prompt=request.negative_prompt, mode="A" # Patch-only mode
strength=request.strength
) )
# Convert result to base64 # Convert result to base64
+48 -32
View File
@@ -26,6 +26,7 @@ class Smart_select_class extends Base_tools_class {
this.currentMask = null; this.currentMask = null;
this.maskCanvas = null; this.maskCanvas = null;
this.isProcessing = false; this.isProcessing = false;
this.selectionBounds = null;
} }
load() { load() {
@@ -137,33 +138,35 @@ class Smart_select_class extends Base_tools_class {
// Store globally for AI inpaint tool to access // Store globally for AI inpaint tool to access
window.smartSelectMask = _this.currentMask; window.smartSelectMask = _this.currentMask;
// Visual feedback - render mask overlay // Calculate selection bounds from mask
_this.renderMaskOverlay(); _this.calculateSelectionBounds();
// Trigger re-render
config.need_render = true; config.need_render = true;
_this.Base_layers.render();
}; };
maskImage.src = 'data:image/png;base64,' + maskBase64; maskImage.src = 'data:image/png;base64,' + maskBase64;
} }
/** /**
* Render a visual overlay showing the selected region * Calculate the bounding box of the selection from the mask
*/ */
renderMaskOverlay() { calculateSelectionBounds() {
if (!this.maskCanvas) return; if (!this.maskCanvas) return;
// Create overlay layer or update existing
// For now, we'll use the selection system
var maskCtx = this.maskCanvas.getContext('2d'); var maskCtx = this.maskCanvas.getContext('2d');
var imageData = maskCtx.getImageData(0, 0, this.maskCanvas.width, this.maskCanvas.height); var imageData = maskCtx.getImageData(0, 0, this.maskCanvas.width, this.maskCanvas.height);
// Find bounding box of selection // Find bounding box of selection
var minX = this.maskCanvas.width, minY = this.maskCanvas.height; var minX = this.maskCanvas.width, minY = this.maskCanvas.height;
var maxX = 0, maxY = 0; var maxX = 0, maxY = 0;
var hasSelection = false;
for (var y = 0; y < this.maskCanvas.height; y++) { for (var y = 0; y < this.maskCanvas.height; y++) {
for (var x = 0; x < this.maskCanvas.width; x++) { for (var x = 0; x < this.maskCanvas.width; x++) {
var i = (y * this.maskCanvas.width + x) * 4; var i = (y * this.maskCanvas.width + x) * 4;
if (imageData.data[i] > 128) { // White pixel in mask if (imageData.data[i] > 128) { // White pixel in mask
hasSelection = true;
minX = Math.min(minX, x); minX = Math.min(minX, x);
minY = Math.min(minY, y); minY = Math.min(minY, y);
maxX = Math.max(maxX, x); maxX = Math.max(maxX, x);
@@ -172,54 +175,66 @@ class Smart_select_class extends Base_tools_class {
} }
} }
if (maxX > minX && maxY > minY) { if (hasSelection && maxX > minX && maxY > minY) {
// Set selection using miniPaint's selection system // Scale to current layer dimensions
var Selection = this.Base_layers.Base_gui?.GUI_tools?.tools_modules?.selection?.object; var scaleX = config.layer.width / config.layer.width_original;
if (Selection) { var scaleY = config.layer.height / config.layer.height_original;
Selection.selection = {
x: config.layer.x + minX * (config.layer.width / config.layer.width_original), this.selectionBounds = {
y: config.layer.y + minY * (config.layer.height / config.layer.height_original), x: config.layer.x + minX * scaleX,
width: (maxX - minX) * (config.layer.width / config.layer.width_original), y: config.layer.y + minY * scaleY,
height: (maxY - minY) * (config.layer.height / config.layer.height_original) width: (maxX - minX) * scaleX,
}; height: (maxY - minY) * scaleY
} };
} }
} }
/**
* Render overlay - called by miniPaint's rendering system
*/
render_overlay(ctx) { render_overlay(ctx) {
// Render marching ants or highlight around selected region
if (!this.currentMask || !this.maskCanvas) return; if (!this.currentMask || !this.maskCanvas) return;
var mouse = this.get_mouse_info(event);
// Draw semi-transparent overlay on non-selected areas // Draw semi-transparent overlay on non-selected areas
ctx.save(); ctx.save();
ctx.globalAlpha = 0.3;
ctx.fillStyle = '#000000';
// 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;
ctx.translate(config.layer.x, config.layer.y); // Create inverse mask canvas (darkens unselected areas)
ctx.scale(scaleX, scaleY);
// Draw inverse mask (darken unselected areas)
var maskCtx = this.maskCanvas.getContext('2d');
var imageData = maskCtx.getImageData(0, 0, this.maskCanvas.width, this.maskCanvas.height);
// Create inverse mask canvas
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');
inverseCtx.fillStyle = '#000000'; // Fill with semi-transparent black
inverseCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
inverseCtx.fillRect(0, 0, inverseCanvas.width, inverseCanvas.height); inverseCtx.fillRect(0, 0, inverseCanvas.width, inverseCanvas.height);
// Cut out the selected area
inverseCtx.globalCompositeOperation = 'destination-out'; inverseCtx.globalCompositeOperation = 'destination-out';
inverseCtx.drawImage(this.maskCanvas, 0, 0); inverseCtx.drawImage(this.maskCanvas, 0, 0);
ctx.drawImage(inverseCanvas, 0, 0); // Draw the overlay on the main canvas
ctx.drawImage(
inverseCanvas,
config.layer.x, config.layer.y,
config.layer.width, config.layer.height
);
// Draw marching ants border around selection
if (this.selectionBounds) {
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(
this.selectionBounds.x,
this.selectionBounds.y,
this.selectionBounds.width,
this.selectionBounds.height
);
}
ctx.restore(); ctx.restore();
} }
@@ -230,6 +245,7 @@ class Smart_select_class extends Base_tools_class {
clearSelection() { clearSelection() {
this.currentMask = null; this.currentMask = null;
this.maskCanvas = null; this.maskCanvas = null;
this.selectionBounds = null;
window.smartSelectMask = null; window.smartSelectMask = null;
config.need_render = true; config.need_render = true;
} }