Skip to content

Commit 47e5ba6

Browse files
flacoonbandi34
authored andcommitted
Add configurable background scaling modes for rembg (Magic Greenscreen)
- Add backgroundMode config parameter with 5 modes: - scale-fill (default): Cover canvas, preserve aspect ratio, may crop - scale-fit: Fit inside canvas, preserve aspect ratio, may have black bars - crop-center: Crop from center to match canvas size - stretch: Stretch to exact size (may distort) - none: Direct copy without scaling (original behavior) - Implement applyBackgroundWithMode() method in Rembg.php - Add admin panel UI (select field in advanced view) - Add config schema validation in RembgConfiguration.php - Add English translations for UI labels and help text - Add documentation in FAQ - Fix memory leak: add imagedestroy() for background image Fixes #1364
1 parent 7c87f4d commit 47e5ba6

File tree

5 files changed

+145
-3
lines changed

5 files changed

+145
-3
lines changed

docs/faq/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,12 @@ Magic Greenscreen is a feature that uses AI to automatically remove backgrounds
543543
- Check "Remove background" to enable the feature
544544
- Optionally configure:
545545
- **Background image:** Path to a custom background image (leave empty for transparent background)
546+
- **Background scaling mode:** How the background image is scaled/cropped (default: scale-fill)
547+
- `scale-fill` (recommended): Covers entire canvas, preserves aspect ratio, may crop edges
548+
- `scale-fit`: Fits inside canvas, preserves aspect ratio, may have black bars
549+
- `crop-center`: Crops from center to exact canvas size
550+
- `stretch`: Stretches to fit (may distort)
551+
- `none`: Direct copy without scaling (original behavior)
546552
- **AI model:** Choose the AI model (default: u2net)
547553
- **Alpha matting:** Enable for better edge quality
548554
- **Alpha matting thresholds:** Fine-tune edge detection (advanced users)

lib/configsetup.inc.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,21 @@
11691169
PathUtility::getAbsolutePath('private/images/background'),
11701170
]
11711171
],
1172+
'rembg_backgroundMode' => [
1173+
'view' => 'advanced',
1174+
'type' => 'select',
1175+
'name' => 'rembg[backgroundMode]',
1176+
'placeholder' => $defaultConfig['rembg']['backgroundMode'],
1177+
'data-theme-field' => 'true',
1178+
'options' => [
1179+
'scale-fill' => 'Scale Fill (Cover canvas, preserve ratio)',
1180+
'scale-fit' => 'Scale Fit (Fit inside, black bars)',
1181+
'crop-center' => 'Crop Center (Cut from center)',
1182+
'stretch' => 'Stretch (Distort to fit)',
1183+
'none' => 'None (Direct copy, no scaling)',
1184+
],
1185+
'value' => $config['rembg']['backgroundMode'],
1186+
],
11721187
'rembg_model' => [
11731188
'view' => 'advanced',
11741189
'type' => 'select',

resources/lang/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@
317317
"magic_greenscreen:rembg_alpha_matting_erode_size": "Alpha matting erode size",
318318
"magic_greenscreen:rembg_alpha_matting_foreground_threshold": "Alpha matting foreground threshold",
319319
"magic_greenscreen:rembg_background": "Background image",
320+
"magic_greenscreen:rembg_backgroundMode": "Background scaling mode",
320321
"magic_greenscreen:rembg_enabled": "Remove background",
321322
"magic_greenscreen:rembg_model": "AI model",
322323
"magic_greenscreen:rembg_post_processing": "Post-processing",
@@ -499,6 +500,7 @@
499500
"manual:magic_greenscreen:rembg_alpha_matting_erode_size": "Erode size for alpha matting.",
500501
"manual:magic_greenscreen:rembg_alpha_matting_foreground_threshold": "Foreground threshold for alpha matting.",
501502
"manual:magic_greenscreen:rembg_background": "Path to the background image that will be used to replace the removed background.",
503+
"manual:magic_greenscreen:rembg_backgroundMode": "Determines how the background image is scaled or cropped. <b>Scale Fill</b> (recommended) covers the entire canvas without distortion. <b>Scale Fit</b> preserves aspect ratio (may have black bars). <b>Crop Center</b> cuts from center. <b>Stretch</b> distorts the image. <b>None</b> copies without scaling.",
502504
"manual:magic_greenscreen:rembg_enabled": "If enabled, the background will be removed using AI.",
503505
"manual:magic_greenscreen:rembg_model": "AI model to use for background removal.",
504506
"manual:magic_greenscreen:rembg_post_processing": "Enable post-processing to improve the result.",

src/Configuration/Section/RembgConfiguration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public static function getNode(): NodeDefinition
1414
->children()
1515
->booleanNode('enabled')->defaultValue(false)->end()
1616
->scalarNode('background')->defaultValue('')->end()
17+
->enumNode('backgroundMode')
18+
->values(['none', 'scale-fit', 'scale-fill', 'crop-center', 'stretch'])
19+
->defaultValue('scale-fill')
20+
->end()
1721
->enumNode('model')
1822
->values(['u2net', 'u2netp', 'u2net_cloth_seg', 'u2net_human_seg', 'silueta', 'isnet_general_use', 'isnet_anime'])
1923
->defaultValue('u2net')

src/Rembg.php

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,18 @@ public static function process(
129129
if ($backgroundContent === false) {
130130
$logger->error('Failed to read background image file');
131131
} else {
132+
$backgroundImage = imagecreatefromstring($backgroundContent);
132133
$backgroundImage = imagecreatefromstring($backgroundContent);
133134
if ($backgroundImage !== false) {
134-
$newImage = imagecreatetruecolor(imagesx($processedImage), imagesy($processedImage));
135-
imagecopy($newImage, $backgroundImage, 0, 0, 0, 0, imagesx($processedImage), imagesy($processedImage));
136-
imagecopy($newImage, $processedImage, 0, 0, 0, 0, imagesx($processedImage), imagesy($processedImage));
135+
$backgroundMode = $rembgConfig['backgroundMode'] ?? 'scale-fill';
136+
$newImage = self::applyBackgroundWithMode(
137+
$processedImage,
138+
$backgroundImage,
139+
$backgroundMode,
140+
$logger
141+
);
137142
imagedestroy($processedImage);
143+
imagedestroy($backgroundImage);
138144
$processedImage = $newImage;
139145
$logger->debug('Background image applied after rembg processing');
140146
}
@@ -161,4 +167,113 @@ public static function process(
161167
return [$imageHandler, $imageResource]; // Fallback to original image
162168
}
163169
}
170+
171+
/**
172+
* Apply background image with different scaling/cropping modes
173+
*
174+
* @param \GdImage $foreground The transparent foreground image
175+
* @param \GdImage $background The background image
176+
* @param string $mode The mode: 'none', 'scale-fit', 'scale-fill', 'crop-center', 'stretch'
177+
* @param \Photobooth\Logger\NamedLogger $logger Logger instance
178+
* @return \GdImage The composited image
179+
*/
180+
private static function applyBackgroundWithMode(
181+
\GdImage $foreground,
182+
\GdImage $background,
183+
string $mode,
184+
\Photobooth\Logger\NamedLogger $logger
185+
): \GdImage {
186+
$canvasWidth = imagesx($foreground);
187+
$canvasHeight = imagesy($foreground);
188+
$bgWidth = imagesx($background);
189+
$bgHeight = imagesy($background);
190+
191+
$canvas = imagecreatetruecolor($canvasWidth, $canvasHeight);
192+
193+
$logger->debug("Applying background with mode: {$mode} (Canvas: {$canvasWidth}x{$canvasHeight}, BG: {$bgWidth}x{$bgHeight})");
194+
195+
switch ($mode) {
196+
case 'none':
197+
// Original behavior: direct copy, no scaling
198+
imagecopy($canvas, $background, 0, 0, 0, 0, $bgWidth, $bgHeight);
199+
break;
200+
201+
case 'scale-fit':
202+
// Scale background to fit inside canvas (preserve aspect ratio, may have black bars)
203+
$scale = min($canvasWidth / $bgWidth, $canvasHeight / $bgHeight);
204+
$scaledWidth = (int)($bgWidth * $scale);
205+
$scaledHeight = (int)($bgHeight * $scale);
206+
$offsetX = (int)(($canvasWidth - $scaledWidth) / 2);
207+
$offsetY = (int)(($canvasHeight - $scaledHeight) / 2);
208+
209+
$black = imagecolorallocate($canvas, 0, 0, 0);
210+
if ($black !== false) {
211+
imagefill($canvas, 0, 0, $black);
212+
}
213+
imagecopyresampled(
214+
$canvas,
215+
$background,
216+
$offsetX,
217+
$offsetY,
218+
0,
219+
0,
220+
$scaledWidth,
221+
$scaledHeight,
222+
$bgWidth,
223+
$bgHeight
224+
);
225+
break;
226+
227+
case 'scale-fill':
228+
// Scale background to cover entire canvas (preserve aspect ratio, may crop)
229+
$scale = max($canvasWidth / $bgWidth, $canvasHeight / $bgHeight);
230+
$scaledWidth = (int)($bgWidth * $scale);
231+
$scaledHeight = (int)($bgHeight * $scale);
232+
$offsetX = (int)(($canvasWidth - $scaledWidth) / 2);
233+
$offsetY = (int)(($canvasHeight - $scaledHeight) / 2);
234+
235+
imagecopyresampled(
236+
$canvas,
237+
$background,
238+
$offsetX,
239+
$offsetY,
240+
0,
241+
0,
242+
$scaledWidth,
243+
$scaledHeight,
244+
$bgWidth,
245+
$bgHeight
246+
);
247+
break;
248+
249+
case 'crop-center':
250+
// Crop background from center to match canvas size
251+
if ($bgWidth < $canvasWidth || $bgHeight < $canvasHeight) {
252+
// Background too small, scale up first
253+
$scale = max($canvasWidth / $bgWidth, $canvasHeight / $bgHeight);
254+
$scaledBg = imagecreatetruecolor((int)($bgWidth * $scale), (int)($bgHeight * $scale));
255+
imagecopyresampled($scaledBg, $background, 0, 0, 0, 0, imagesx($scaledBg), imagesy($scaledBg), $bgWidth, $bgHeight);
256+
$cropX = (int)((imagesx($scaledBg) - $canvasWidth) / 2);
257+
$cropY = (int)((imagesy($scaledBg) - $canvasHeight) / 2);
258+
imagecopy($canvas, $scaledBg, 0, 0, $cropX, $cropY, $canvasWidth, $canvasHeight);
259+
imagedestroy($scaledBg);
260+
} else {
261+
$cropX = (int)(($bgWidth - $canvasWidth) / 2);
262+
$cropY = (int)(($bgHeight - $canvasHeight) / 2);
263+
imagecopy($canvas, $background, 0, 0, $cropX, $cropY, $canvasWidth, $canvasHeight);
264+
}
265+
break;
266+
267+
case 'stretch':
268+
default:
269+
// Stretch background to exact canvas size (may distort)
270+
imagecopyresampled($canvas, $background, 0, 0, 0, 0, $canvasWidth, $canvasHeight, $bgWidth, $bgHeight);
271+
break;
272+
}
273+
274+
// Merge transparent foreground onto background
275+
imagecopy($canvas, $foreground, 0, 0, 0, 0, $canvasWidth, $canvasHeight);
276+
277+
return $canvas;
278+
}
164279
}

0 commit comments

Comments
 (0)