| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import React, { useState, useCallback, useRef } from 'react'; |
| | import axios from 'axios'; |
| |
|
| | |
| | const API_BASE_URL = process.env.REACT_APP_BACKGROUNDFX_API_URL || 'https://api.backgroundfx.pro/v1'; |
| | const API_KEY = process.env.REACT_APP_BACKGROUNDFX_API_KEY; |
| |
|
| | |
| | |
| | |
| | const useBackgroundFX = (apiKey) => { |
| | const [isProcessing, setIsProcessing] = useState(false); |
| | const [progress, setProgress] = useState(0); |
| | const [error, setError] = useState(null); |
| |
|
| | const client = useRef( |
| | axios.create({ |
| | baseURL: API_BASE_URL, |
| | headers: { |
| | 'Authorization': `Bearer ${apiKey}`, |
| | }, |
| | }) |
| | ); |
| |
|
| | const removeBackground = useCallback(async (file, options = {}) => { |
| | setIsProcessing(true); |
| | setError(null); |
| | setProgress(0); |
| |
|
| | const formData = new FormData(); |
| | formData.append('file', file); |
| | Object.keys(options).forEach(key => { |
| | formData.append(key, options[key]); |
| | }); |
| |
|
| | try { |
| | const response = await client.current.post('/process/remove-background', formData, { |
| | onUploadProgress: (progressEvent) => { |
| | const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); |
| | setProgress(percentCompleted); |
| | }, |
| | }); |
| |
|
| | setIsProcessing(false); |
| | setProgress(100); |
| | return response.data; |
| | } catch (err) { |
| | setError(err.response?.data?.message || err.message); |
| | setIsProcessing(false); |
| | throw err; |
| | } |
| | }, []); |
| |
|
| | return { |
| | removeBackground, |
| | isProcessing, |
| | progress, |
| | error, |
| | }; |
| | }; |
| |
|
| | |
| | |
| | |
| | const DropZone = ({ onDrop, disabled }) => { |
| | const [isDragging, setIsDragging] = useState(false); |
| | const fileInputRef = useRef(null); |
| |
|
| | const handleDragEnter = useCallback((e) => { |
| | e.preventDefault(); |
| | e.stopPropagation(); |
| | setIsDragging(true); |
| | }, []); |
| |
|
| | const handleDragLeave = useCallback((e) => { |
| | e.preventDefault(); |
| | e.stopPropagation(); |
| | setIsDragging(false); |
| | }, []); |
| |
|
| | const handleDragOver = useCallback((e) => { |
| | e.preventDefault(); |
| | e.stopPropagation(); |
| | }, []); |
| |
|
| | const handleDrop = useCallback((e) => { |
| | e.preventDefault(); |
| | e.stopPropagation(); |
| | setIsDragging(false); |
| |
|
| | const files = e.dataTransfer.files; |
| | if (files && files[0]) { |
| | onDrop(files[0]); |
| | } |
| | }, [onDrop]); |
| |
|
| | const handleFileSelect = useCallback((e) => { |
| | const files = e.target.files; |
| | if (files && files[0]) { |
| | onDrop(files[0]); |
| | } |
| | }, [onDrop]); |
| |
|
| | return ( |
| | <div |
| | className={`dropzone ${isDragging ? 'dragging' : ''} ${disabled ? 'disabled' : ''}`} |
| | onDragEnter={handleDragEnter} |
| | onDragLeave={handleDragLeave} |
| | onDragOver={handleDragOver} |
| | onDrop={handleDrop} |
| | onClick={() => !disabled && fileInputRef.current?.click()} |
| | style={{ |
| | border: '2px dashed #ccc', |
| | borderRadius: '8px', |
| | padding: '40px', |
| | textAlign: 'center', |
| | cursor: disabled ? 'not-allowed' : 'pointer', |
| | backgroundColor: isDragging ? '#f0f0f0' : 'white', |
| | transition: 'all 0.3s ease', |
| | }} |
| | > |
| | <input |
| | ref={fileInputRef} |
| | type="file" |
| | accept="image/*" |
| | onChange={handleFileSelect} |
| | style={{ display: 'none' }} |
| | disabled={disabled} |
| | /> |
| | |
| | <div className="dropzone-content"> |
| | <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor"> |
| | <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> |
| | <polyline points="17 8 12 3 7 8" /> |
| | <line x1="12" y1="3" x2="12" y2="15" /> |
| | </svg> |
| | <p style={{ marginTop: '16px', fontSize: '18px', fontWeight: '500' }}> |
| | {disabled ? 'Processing...' : 'Drop image here or click to browse'} |
| | </p> |
| | <p style={{ marginTop: '8px', fontSize: '14px', color: '#666' }}> |
| | Supports PNG, JPG, WebP (max 50MB) |
| | </p> |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | |
| | |
| | |
| | const ImagePreview = ({ originalUrl, processedUrl, maskUrl }) => { |
| | const [viewMode, setViewMode] = useState('processed'); |
| |
|
| | return ( |
| | <div className="image-preview" style={{ marginTop: '20px' }}> |
| | <div className="preview-controls" style={{ marginBottom: '16px' }}> |
| | <button |
| | onClick={() => setViewMode('original')} |
| | style={{ |
| | padding: '8px 16px', |
| | marginRight: '8px', |
| | backgroundColor: viewMode === 'original' ? '#007bff' : '#f0f0f0', |
| | color: viewMode === 'original' ? 'white' : 'black', |
| | border: 'none', |
| | borderRadius: '4px', |
| | cursor: 'pointer', |
| | }} |
| | > |
| | Original |
| | </button> |
| | <button |
| | onClick={() => setViewMode('processed')} |
| | style={{ |
| | padding: '8px 16px', |
| | marginRight: '8px', |
| | backgroundColor: viewMode === 'processed' ? '#007bff' : '#f0f0f0', |
| | color: viewMode === 'processed' ? 'white' : 'black', |
| | border: 'none', |
| | borderRadius: '4px', |
| | cursor: 'pointer', |
| | }} |
| | > |
| | Processed |
| | </button> |
| | {maskUrl && ( |
| | <button |
| | onClick={() => setViewMode('mask')} |
| | style={{ |
| | padding: '8px 16px', |
| | backgroundColor: viewMode === 'mask' ? '#007bff' : '#f0f0f0', |
| | color: viewMode === 'mask' ? 'white' : 'black', |
| | border: 'none', |
| | borderRadius: '4px', |
| | cursor: 'pointer', |
| | }} |
| | > |
| | Mask |
| | </button> |
| | )} |
| | </div> |
| | |
| | <div className="preview-image" style={{ position: 'relative', display: 'inline-block' }}> |
| | {viewMode === 'original' && originalUrl && ( |
| | <img src={originalUrl} alt="Original" style={{ maxWidth: '100%', height: 'auto' }} /> |
| | )} |
| | {viewMode === 'processed' && processedUrl && ( |
| | <img src={processedUrl} alt="Processed" style={{ maxWidth: '100%', height: 'auto' }} /> |
| | )} |
| | {viewMode === 'mask' && maskUrl && ( |
| | <img src={maskUrl} alt="Mask" style={{ maxWidth: '100%', height: 'auto' }} /> |
| | )} |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | |
| | |
| | |
| | const ProgressBar = ({ progress }) => { |
| | return ( |
| | <div className="progress-bar" style={{ marginTop: '20px' }}> |
| | <div |
| | style={{ |
| | width: '100%', |
| | height: '8px', |
| | backgroundColor: '#f0f0f0', |
| | borderRadius: '4px', |
| | overflow: 'hidden', |
| | }} |
| | > |
| | <div |
| | style={{ |
| | width: `${progress}%`, |
| | height: '100%', |
| | backgroundColor: '#007bff', |
| | transition: 'width 0.3s ease', |
| | }} |
| | /> |
| | </div> |
| | <p style={{ marginTop: '8px', textAlign: 'center', fontSize: '14px' }}> |
| | {progress}% Complete |
| | </p> |
| | </div> |
| | ); |
| | }; |
| |
|
| | |
| | |
| | |
| | const BackgroundRemover = ({ apiKey = API_KEY }) => { |
| | const [originalImage, setOriginalImage] = useState(null); |
| | const [processedImage, setProcessedImage] = useState(null); |
| | const [maskImage, setMaskImage] = useState(null); |
| | const [selectedQuality, setSelectedQuality] = useState('high'); |
| | const [selectedModel, setSelectedModel] = useState('auto'); |
| | const [returnMask, setReturnMask] = useState(false); |
| |
|
| | const { removeBackground, isProcessing, progress, error } = useBackgroundFX(apiKey); |
| |
|
| | const handleFileDrop = useCallback(async (file) => { |
| | |
| | if (!file.type.startsWith('image/')) { |
| | alert('Please upload an image file'); |
| | return; |
| | } |
| |
|
| | if (file.size > 50 * 1024 * 1024) { |
| | alert('File size must be less than 50MB'); |
| | return; |
| | } |
| |
|
| | |
| | const reader = new FileReader(); |
| | reader.onload = (e) => { |
| | setOriginalImage(e.target.result); |
| | }; |
| | reader.readAsDataURL(file); |
| |
|
| | |
| | try { |
| | const result = await removeBackground(file, { |
| | quality: selectedQuality, |
| | model: selectedModel, |
| | return_mask: returnMask, |
| | }); |
| |
|
| | setProcessedImage(result.image); |
| | if (result.mask) { |
| | setMaskImage(result.mask); |
| | } |
| | } catch (err) { |
| | console.error('Processing failed:', err); |
| | } |
| | }, [removeBackground, selectedQuality, selectedModel, returnMask]); |
| |
|
| | const handleDownload = useCallback((url, filename) => { |
| | const link = document.createElement('a'); |
| | link.href = url; |
| | link.download = filename; |
| | document.body.appendChild(link); |
| | link.click(); |
| | document.body.removeChild(link); |
| | }, []); |
| |
|
| | const handleReset = useCallback(() => { |
| | setOriginalImage(null); |
| | setProcessedImage(null); |
| | setMaskImage(null); |
| | }, []); |
| |
|
| | return ( |
| | <div className="background-remover" style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}> |
| | <h1 style={{ textAlign: 'center', marginBottom: '32px' }}> |
| | BackgroundFX Pro - React Example |
| | </h1> |
| | |
| | {/* Settings */} |
| | <div className="settings" style={{ marginBottom: '24px' }}> |
| | <div style={{ marginBottom: '16px' }}> |
| | <label style={{ marginRight: '16px' }}> |
| | Quality: |
| | <select |
| | value={selectedQuality} |
| | onChange={(e) => setSelectedQuality(e.target.value)} |
| | style={{ marginLeft: '8px', padding: '4px 8px' }} |
| | > |
| | <option value="low">Low</option> |
| | <option value="medium">Medium</option> |
| | <option value="high">High</option> |
| | <option value="ultra">Ultra</option> |
| | </select> |
| | </label> |
| | |
| | <label style={{ marginRight: '16px' }}> |
| | Model: |
| | <select |
| | value={selectedModel} |
| | onChange={(e) => setSelectedModel(e.target.value)} |
| | style={{ marginLeft: '8px', padding: '4px 8px' }} |
| | > |
| | <option value="auto">Auto</option> |
| | <option value="rembg">RemBG</option> |
| | <option value="u2net">U2Net</option> |
| | <option value="sam2">SAM2</option> |
| | </select> |
| | </label> |
| | |
| | <label> |
| | <input |
| | type="checkbox" |
| | checked={returnMask} |
| | onChange={(e) => setReturnMask(e.target.checked)} |
| | style={{ marginRight: '8px' }} |
| | /> |
| | Return Mask |
| | </label> |
| | </div> |
| | </div> |
| | |
| | {/* Drop Zone */} |
| | {!originalImage && ( |
| | <DropZone onDrop={handleFileDrop} disabled={isProcessing} /> |
| | )} |
| | |
| | {/* Progress Bar */} |
| | {isProcessing && <ProgressBar progress={progress} />} |
| | |
| | {/* Error Message */} |
| | {error && ( |
| | <div |
| | style={{ |
| | marginTop: '16px', |
| | padding: '12px', |
| | backgroundColor: '#fee', |
| | border: '1px solid #fcc', |
| | borderRadius: '4px', |
| | color: '#c00', |
| | }} |
| | > |
| | Error: {error} |
| | </div> |
| | )} |
| | |
| | {/* Image Preview */} |
| | {(originalImage || processedImage) && ( |
| | <ImagePreview |
| | originalUrl={originalImage} |
| | processedUrl={processedImage} |
| | maskUrl={maskImage} |
| | /> |
| | )} |
| | |
| | {/* Action Buttons */} |
| | {processedImage && ( |
| | <div className="actions" style={{ marginTop: '24px', textAlign: 'center' }}> |
| | <button |
| | onClick={() => handleDownload(processedImage, 'processed.png')} |
| | style={{ |
| | padding: '10px 20px', |
| | marginRight: '8px', |
| | backgroundColor: '#28a745', |
| | color: 'white', |
| | border: 'none', |
| | borderRadius: '4px', |
| | cursor: 'pointer', |
| | }} |
| | > |
| | Download Result |
| | </button> |
| | |
| | {maskImage && ( |
| | <button |
| | onClick={() => handleDownload(maskImage, 'mask.png')} |
| | style={{ |
| | padding: '10px 20px', |
| | marginRight: '8px', |
| | backgroundColor: '#17a2b8', |
| | color: 'white', |
| | border: 'none', |
| | borderRadius: '4px', |
| | cursor: 'pointer', |
| | }} |
| | > |
| | Download Mask |
| | </button> |
| | )} |
| | |
| | <button |
| | onClick={handleReset} |
| | style={{ |
| | padding: '10px 20px', |
| | backgroundColor: '#dc3545', |
| | color: 'white', |
| | border: 'none', |
| | borderRadius: '4px', |
| | cursor: 'pointer', |
| | }} |
| | > |
| | Process Another |
| | </button> |
| | </div> |
| | )} |
| | |
| | {/* Instructions */} |
| | <div className="instructions" style={{ marginTop: '40px', padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}> |
| | <h3>How to use:</h3> |
| | <ol> |
| | <li>Select quality and model settings</li> |
| | <li>Drag and drop an image or click to browse</li> |
| | <li>Wait for processing to complete</li> |
| | <li>Preview and download your results</li> |
| | </ol> |
| | |
| | <h3>Integration:</h3> |
| | <pre style={{ backgroundColor: '#fff', padding: '12px', borderRadius: '4px', overflow: 'auto' }}> |
| | {`npm install axios |
| | import BackgroundRemover from './BackgroundRemover'; |
| | |
| | <BackgroundRemover apiKey="your-api-key" />`} |
| | </pre> |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | export default BackgroundRemover; |
| | export { useBackgroundFX, DropZone, ImagePreview, ProgressBar }; |