Merge pull request #29 from outis1one/claude/migrate-to-minipaint-eYKWf
Add Remove Background feature using AI (rembg)
This commit is contained in:
@@ -33,6 +33,10 @@ class InpaintRequest(BaseModel):
|
|||||||
guidance_scale: Optional[float] = 7.5
|
guidance_scale: Optional[float] = 7.5
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveBackgroundRequest(BaseModel):
|
||||||
|
image: str # Base64 encoded image
|
||||||
|
|
||||||
|
|
||||||
@router.post("/smart-select-base64")
|
@router.post("/smart-select-base64")
|
||||||
async def smart_select_base64(request: SmartSelectRequest):
|
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))
|
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")
|
@router.post("/remove-background")
|
||||||
async def remove_background(
|
async def remove_background(
|
||||||
project_id: Optional[int] = Form(None),
|
project_id: Optional[int] = Form(None),
|
||||||
|
|||||||
@@ -283,6 +283,14 @@ const menuDefinition = [
|
|||||||
name: 'Histogram',
|
name: 'Histogram',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
target: 'image/histogram.histogram'
|
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();
|
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
|
* Health check for the backend
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
|
|||||||
Reference in New Issue
Block a user