creek

The AI Image Editor of 2030
commit 471a07f56fb160ea08c39a315b4d1605dd8a070f
parent de68e7ca01424ff4e016ea3556f33a33f15287bc
Author: maydayv7 <maydayv7@gmail.com>
Date:   Wed,  3 Dec 2025 18:43:57 +0530

Remove background banner

And some minor fixes

Diffstat:
M.env | 21++++++---------------
Mlib/services/flask_service.dart | 10++--------
Mlib/ui/pages/canvas_board_page.dart | 152++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mlib/ui/pages/canvas_toolbar/magic_draw_overlay.dart | 10++++++++--
Mlib/ui/pages/define_brand_page.dart | 10++++++++--
5 files changed, 159 insertions(+), 44 deletions(-)

diff --git a/.env b/.env @@ -1,14 +1,6 @@ -# Modal Server -# URL_ASSET=https://maydayv7--adobe-flask-modelbackend-asset.modal.run -# URL_DESCRIBE=https://maydayv7--adobe-flask-modelbackend-describe.modal.run -# URL_GENERATE=https://maydayv7--adobe-flask-modelbackend-generate.modal.run -# URL_INPAINTING=https://maydayv7--adobe-flask-modelbackend-inpainting.modal.run -# URL_INPAINTING_API=https://maydayv7--adobe-flask-modelbackend-inpainting-api.modal.run - -# Abhinav's PC -URL_ASSET=https://locustlike-trieciously-rudolph.ngrok-free.dev/asset -URL_DESCRIBE=https://locustlike-trieciously-rudolph.ngrok-free.dev/describe -URL_GENERATE=https://locustlike-trieciously-rudolph.ngrok-free.dev/generate -URL_INPAINTING=https://locustlike-trieciously-rudolph.ngrok-free.dev/inpainting -URL_INPAINTING_API=https://locustlike-trieciously-rudolph.ngrok-free.dev/inpainting-api -URL_SKETCH_API=https://locustlike-trieciously-rudolph.ngrok-free.dev/sketch-api -\ No newline at end of file +URL_ASSET=https://maydayv7--adobe-flask-modelbackend-asset.modal.run +URL_DESCRIBE=https://maydayv7--adobe-flask-modelbackend-describe.modal.run +URL_GENERATE=https://maydayv7--adobe-flask-modelbackend-generate.modal.run +URL_INPAINTING=https://maydayv7--adobe-flask-modelbackend-inpainting.modal.run +URL_INPAINTING_API=https://maydayv7--adobe-flask-modelbackend-inpainting-api.modal.run +URL_SKETCH_API=https://maydayv7--adobe-flask-modelbackend-sketch-api.modal.run diff --git a/lib/services/flask_service.dart b/lib/services/flask_service.dart @@ -94,10 +94,7 @@ class FlaskService { return null; } - // 3. Remove Background - debugPrint("🔗 [Pipeline] Removing background from generated result..."); - - return generateAsset(imagePath: generatedImagePath); + return generatedImagePath; } // =========================================================================== @@ -223,10 +220,7 @@ class FlaskService { return null; } - // 3. Remove Background (Pipeline Extension) - debugPrint("🔗 [Pipeline] Removing background from generated result..."); - - return generateAsset(imagePath: generatedImagePath); + return generatedImagePath; } /// [Background Removal] diff --git a/lib/ui/pages/canvas_board_page.dart b/lib/ui/pages/canvas_board_page.dart @@ -129,7 +129,14 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { // Magic Draw / Inpainting State File? _tempBaseImage; bool _isInpainting = false; - bool _isCapturingBase = false; // New flag to hide strokes during capture + bool _isCapturingBase = false; // Hide strokes during capture + + // Background Removal Banner State + bool _showBgRemovalBanner = false; + String? _bgRemovalTargetId; + String? _bgRemovalTargetPath; + Timer? _bgRemovalBannerTimer; + bool _isRemovingBg = false; String? selectedId; late Size _canvasSize; @@ -193,6 +200,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { @override void dispose() { _inactivityTimer?.cancel(); + _bgRemovalBannerTimer?.cancel(); _textEditingController.dispose(); _textFocusNode.dispose(); _transformationController.dispose(); @@ -1019,7 +1027,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { children: [ GestureDetector( onTap: () { - // <--- CHANGED: Allow deselecting if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON if (!_isMagicDrawActive || _isMagicPanelDisabled) { _exitEditMode(); setState(() => selectedId = null); @@ -1032,7 +1039,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { boundaryMargin: const EdgeInsets.all(double.infinity), minScale: 0.01, maxScale: 10.0, - // <--- CHANGED: Enable Zoom/Pan if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON scaleEnabled: !_isMagicDrawActive || _isMagicPanelDisabled, panEnabled: !_isMagicDrawActive || _isMagicPanelDisabled, child: RepaintBoundary( @@ -1067,8 +1073,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { type: e['type'], content: e['content'], styleData: e, - // <--- OPTIONAL: If you want to move elements while Hand is active, change this line too: - // isSelected: isSelected && (!_isMagicDrawActive || _isMagicPanelDisabled), isSelected: isSelected && !_isMagicDrawActive, isEditing: isSelected && @@ -1122,7 +1126,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { }), // Drawing Layer IgnorePointer( - // <--- CHANGED: Ignore touches (disable drawing) if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON ignoring: !_isMagicDrawActive || _isMagicPanelDisabled, child: RepaintBoundary( @@ -1162,8 +1165,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ), ), - // ... (Rest of your UI: AI Description, MagicDrawTools, etc.) ... - // I have truncated the bottom part as it remains unchanged. if (_aiDescription != null && _isMagicDrawActive) Positioned( top: 10, @@ -1281,6 +1282,58 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { (s) => _updateSelectedTextProperty('style_fontSize', s), ), + // Background Removal Banner + if (_showBgRemovalBanner) + Positioned( + top: SafeArea(child: Container()).minimum.top + 10, + left: 0, + right: 0, + child: Center( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow(color: Colors.black26, blurRadius: 8, offset: Offset(0, 2)) + ] + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (_isRemovingBg) ...[ + const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)), + const SizedBox(width: 8), + const Text("Removing...", style: TextStyle(color: Colors.white, fontFamily: 'GeneralSans', fontSize: 14)), + ] else ...[ + const Text( + "Remove background?", + style: TextStyle(color: Colors.white, fontFamily: 'GeneralSans', fontSize: 14), + ), + const SizedBox(width: 8), + InkWell( + onTap: _confirmRemoveBackground, + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: const Icon(Icons.check, size: 16, color: Colors.black), + ), + ), + const SizedBox(width: 8), + InkWell( + onTap: () => setState(() => _showBgRemovalBanner = false), + child: const Icon(Icons.close, size: 18, color: Colors.white54), + ), + ] + ], + ), + ), + ), + ), + Positioned( bottom: 0, left: 0, @@ -1398,6 +1451,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ), ); } + // --- DRAWING HELPERS --- void _onPanUpdate(DragUpdateDetails details) { @@ -1451,7 +1505,65 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { }); } + // --- BACKGROUND REMOVAL LOGIC --- + + void _triggerBgRemovalBanner(String elementId, String imagePath) { + _bgRemovalBannerTimer?.cancel(); + setState(() { + _showBgRemovalBanner = true; + _bgRemovalTargetId = elementId; + _bgRemovalTargetPath = imagePath; + }); + + // Automatically hide after 10 seconds + _bgRemovalBannerTimer = Timer(const Duration(seconds: 10), () { + if (mounted) { + setState(() => _showBgRemovalBanner = false); + } + }); + } + + Future<void> _confirmRemoveBackground() async { + if (_bgRemovalTargetId == null || _bgRemovalTargetPath == null) return; + + _bgRemovalBannerTimer?.cancel(); + + setState(() { + _isRemovingBg = true; + }); + + try { + // Call service to remove background + final newPath = await FlaskService().generateAsset(imagePath: _bgRemovalTargetPath!); + + if (newPath != null && mounted) { + // Find the element and update its content + final index = elements.indexWhere((e) => e['id'] == _bgRemovalTargetId); + if (index != -1) { + final oldState = _getCurrentState(); + setState(() { + elements[index]['content'] = newPath; // Replace with transparent png + _showBgRemovalBanner = false; + _isRemovingBg = false; + _hasUnsavedChanges = true; + }); + _recordChange(oldState); + } + } + } catch (e) { + debugPrint("BG Removal Failed: $e"); + } finally { + if (mounted) setState(() { + _isRemovingBg = false; + _showBgRemovalBanner = false; + }); + } + } + Future<void> _processInpainting(String prompt, String modelId) async { + // Lock editing and hide keyboard + FocusScope.of(context).unfocus(); + setState(() => _isInpainting = true); _resetInactivityTimer(); @@ -1479,15 +1591,15 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { // CASE 1: INPAINTING (When hasImageLayers is TRUE) if (hasImageLayers) { if (modelId == 'inpaint_api') { - // Call API Inpainting - newImageUrl = await FlaskService().inpaintApiImage( + // Call API Inpainting + newImageUrl = await FlaskService().inpaintApiImage( imagePath: _tempBaseImage!.path, maskPath: maskFile.path, prompt: prompt, ); } else { - // Default: Standard Flask Inpainting ('inpaint_standard') - newImageUrl = await FlaskService().inpaintImage( + // Default: Standard Flask Inpainting ('inpaint_standard') + newImageUrl = await FlaskService().inpaintImage( imagePath: _tempBaseImage!.path, maskPath: maskFile.path, prompt: prompt, @@ -1514,7 +1626,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { option: 1, imageDescription: _aiDescription, ); - } else if (modelId == 'sketch_creative') { newImageUrl = await FlaskService().sketchToImageAPI( @@ -1529,10 +1640,15 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { } if (newImageUrl != null) { - _addGeneratedImage(newImageUrl); + final id = _addGeneratedImage(newImageUrl); setState(() { _isMagicDrawActive = false; }); + + // Trigger Banner for Background Removal (Only if it was a generation, not inpainting) + if (!hasImageLayers) { + _triggerBgRemovalBanner(id, newImageUrl); + } } } catch (e) { debugPrint("Generation Error: $e"); @@ -1549,22 +1665,24 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { } } - void _addGeneratedImage(String? newImageUrl) { + String _addGeneratedImage(String? newImageUrl) { + String id = ''; if (newImageUrl != null) { debugPrint("✅ Adding generated image to canvas: $newImageUrl"); + id = 'gen_${DateTime.now().millisecondsSinceEpoch}'; setState(() { elements.add({ - 'id': 'gen_${DateTime.now().millisecondsSinceEpoch}', + 'id': id, 'type': 'file_image', 'content': newImageUrl, 'position': const Offset(0, 0), 'size': _canvasSize, 'rotation': 0.0, }); - // Clear magic paths now that operation is done _magicPaths.clear(); }); } + return id; } // Updated to generate mask as: Base Image + Drawing Strokes diff --git a/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart b/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart @@ -27,7 +27,7 @@ class MagicDrawTools extends StatefulWidget { final Function(bool) onMagicPanelActivityToggle; final bool isMagicPanelDisabled; - // NEW: To decide which dropdown list to show + // To decide which dropdown list to show final bool hasImageLayers; const MagicDrawTools({ @@ -46,7 +46,7 @@ class MagicDrawTools extends StatefulWidget { required this.onViewModeToggle, required this.onMagicPanelActivityToggle, required this.isMagicPanelDisabled, - required this.hasImageLayers, // <-- Added + required this.hasImageLayers, }); @override @@ -103,6 +103,11 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { if (oldWidget.hasImageLayers != widget.hasImageLayers) { _updateSelectedModelGroup(); } + + // Clear prompt when opening the tool + if (!oldWidget.isActive && widget.isActive) { + _promptController.clear(); + } } void _updateSelectedModelGroup() { @@ -330,6 +335,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { Expanded( child: TextField( controller: _promptController, + enabled: !widget.isProcessing, onSubmitted: (_) => _handleSubmit(), decoration: const InputDecoration.collapsed( hintText: "tap imagination...", diff --git a/lib/ui/pages/define_brand_page.dart b/lib/ui/pages/define_brand_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:adobe/ui/styles/variables.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../services/project_service.dart'; +import 'project_detail_page.dart'; class DefineBrandPage extends StatefulWidget { final String projectName; @@ -82,9 +83,14 @@ class _DefineBrandPageState extends State<DefineBrandPage> { description: description.isEmpty ? null : description, ); - // 4. Success Handling - Return the ID and Title map + // 4. Navigate to ProjectDetailPage if (mounted) { - Navigator.pop(context, {'id': newId, 'title': title}); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ProjectDetailPage(projectId: newId), + ), + ); } } catch (e) { // 5. Error Handling