Add Remove Background feature using AI (rembg)
Frontend: - Added "Remove Background (AI)" to Image menu - Created remove_background.js module with dialog options - Added removeBackground method to API service Backend: - Added /tools/remove-background-base64 endpoint for miniPaint frontend - Uses rembg library for AI-powered background removal Features: - Automatically detects main subject and removes background - Option to create as new layer or replace current - Enables transparency mode after removal - Works with any image layer
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
@@ -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>}
|
||||
|
||||
Reference in New Issue
Block a user