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

Add Remove Background feature using AI (rembg)
This commit is contained in:
Outis
2026-01-26 19:54:01 -05:00
committed by GitHub
4 changed files with 221 additions and 0 deletions
+44
View File
@@ -33,6 +33,10 @@ class InpaintRequest(BaseModel):
guidance_scale: Optional[float] = 7.5
class RemoveBackgroundRequest(BaseModel):
image: str # Base64 encoded image
@router.post("/smart-select-base64")
async def smart_select_base64(request: SmartSelectRequest):
"""
@@ -124,6 +128,46 @@ async def inpaint_base64(request: InpaintRequest):
raise HTTPException(status_code=500, detail=str(e))
@router.post("/remove-background-base64")
async def remove_background_base64(request: RemoveBackgroundRequest):
"""
Remove background from a base64 encoded image using rembg.
Returns base64 encoded PNG with transparent background.
Used by miniPaint frontend.
"""
try:
from rembg import remove
except ImportError:
raise HTTPException(
status_code=500,
detail="rembg not installed. Run: pip install rembg"
)
try:
# Decode base64 image
image_bytes = base64.b64decode(request.image)
# Remove background
result_bytes = remove(image_bytes)
# Convert result to base64
result_b64 = base64.b64encode(result_bytes).decode('utf-8')
# Get dimensions
img = Image.open(BytesIO(result_bytes))
return {
"result": result_b64,
"width": img.width,
"height": img.height
}
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
@router.post("/remove-background")
async def remove_background(
project_id: Optional[int] = Form(None),
+8
View File
@@ -283,6 +283,14 @@ const menuDefinition = [
name: 'Histogram',
ellipsis: true,
target: 'image/histogram.histogram'
},
{
divider: true
},
{
name: 'Remove Background (AI)',
ellipsis: true,
target: 'image/remove_background.remove_background'
}
]
},
@@ -0,0 +1,145 @@
/**
* Remove Background Module - Uses AI to remove background and create transparent layer
*/
import app from './../../app.js';
import config from './../../config.js';
import Base_layers_class from './../../core/base-layers.js';
import Dialog_class from './../../libs/popup.js';
import Helper_class from './../../libs/helpers.js';
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
import apiService from './../../services/api.js';
var instance = null;
class Image_remove_background_class {
constructor() {
// Singleton
if (instance) {
return instance;
}
instance = this;
this.Base_layers = new Base_layers_class();
this.Helper = new Helper_class();
this.Dialog = new Dialog_class();
this.isProcessing = false;
}
/**
* Remove background from current layer
* Creates a new layer with transparent background
*/
async remove_background() {
var _this = this;
if (this.isProcessing) {
alertify.warning('Already processing... please wait');
return;
}
// Check if current layer is an image
if (config.layer.type != 'image') {
alertify.error('Current layer must be an image');
return;
}
var settings = {
title: 'Remove Background',
params: [
{ name: "info", title: "AI will detect the main subject and remove the background.", type: "label" },
{ name: "new_layer", title: "Create as new layer:", value: true },
{ name: "trim_result", title: "Trim transparent edges:", value: false },
],
on_finish: async function (params) {
await _this.do_remove_background(params);
},
};
this.Dialog.show(settings);
}
async do_remove_background(params) {
this.isProcessing = true;
alertify.message('AI is removing background... this may take a moment');
try {
// Get current layer image as base64
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = config.layer.width_original;
canvas.height = config.layer.height_original;
ctx.drawImage(config.layer.link, 0, 0);
var imageData = canvas.toDataURL('image/png').split(',')[1];
// Call backend API
var result = await apiService.removeBackground(imageData);
// Create image from result
var resultImage = new Image();
resultImage.onload = () => {
if (params.new_layer) {
// Create as new layer
var layerParams = {
x: config.layer.x,
y: config.layer.y,
width: resultImage.width,
height: resultImage.height,
width_original: resultImage.width,
height_original: resultImage.height,
type: 'image',
name: config.layer.name + ' (No BG)',
data: 'data:image/png;base64,' + result.result
};
app.State.do_action(
new app.Actions.Bundle_action('remove_background', 'Remove Background', [
new app.Actions.Insert_layer_action(layerParams)
])
);
alertify.success('Background removed! New layer created.');
} else {
// Replace current layer
var newCanvas = document.createElement('canvas');
newCanvas.width = resultImage.width;
newCanvas.height = resultImage.height;
var newCtx = newCanvas.getContext('2d');
newCtx.drawImage(resultImage, 0, 0);
app.State.do_action(
new app.Actions.Bundle_action('remove_background', 'Remove Background', [
new app.Actions.Update_layer_image_action(newCanvas, config.layer.id)
])
);
alertify.success('Background removed!');
}
// Enable transparency if not already
if (config.TRANSPARENCY == false) {
config.TRANSPARENCY = true;
this.Base_layers.render();
alertify.message('Transparency enabled to show removed background');
}
this.isProcessing = false;
};
resultImage.onerror = () => {
alertify.error('Failed to load result image');
this.isProcessing = false;
};
resultImage.src = 'data:image/png;base64,' + result.result;
} catch (error) {
console.error('Remove background error:', error);
alertify.error('Failed to remove background: ' + error.message);
this.isProcessing = false;
}
}
}
export default Image_remove_background_class;
+24
View File
@@ -71,6 +71,30 @@ class ApiService {
return response.json();
}
/**
* Remove background from image using AI (rembg)
* @param {string} imageData - Base64 encoded image data
* @returns {Promise<{result: string, width: number, height: number}>} - Base64 encoded result with transparency
*/
async removeBackground(imageData) {
const response = await fetch(`${this.baseUrl}/tools/remove-background-base64`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
image: imageData,
}),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ detail: 'Unknown error' }));
throw new Error(error.detail || `Remove background request failed: ${response.status}`);
}
return response.json();
}
/**
* Health check for the backend
* @returns {Promise<boolean>}