feat: add contrast/sharpen strength controls, disable binarization
Major improvements to preprocessing controls: Backend: - Add contrast_strength (0.5-3.0) and sharpen_strength (0.5-2.0) to PreprocessingConfig - Auto-detection now calculates optimal strength based on image quality metrics: - Lower contrast → Higher contrast_strength - Lower edge strength → Higher sharpen_strength - Disable binarization in auto mode (rarely beneficial) - CLAHE clipLimit now scales with contrast_strength - Sharpening uses unsharp mask with variable strength Frontend: - Add strength sliders for contrast and sharpen in manual mode - Sliders show current value and strength level (輕微/正常/強/最強) - Move binarize option to collapsible "進階選項" section - Updated i18n translations for strength labels 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,20 @@ export default function PreprocessingSettings({
|
||||
onConfigChange({ ...config, [field]: value })
|
||||
}
|
||||
|
||||
const getStrengthLabel = (value: number, type: 'contrast' | 'sharpen') => {
|
||||
if (type === 'contrast') {
|
||||
if (value <= 0.75) return t('processing.preprocessing.strength.subtle')
|
||||
if (value <= 1.25) return t('processing.preprocessing.strength.normal')
|
||||
if (value <= 2.0) return t('processing.preprocessing.strength.strong')
|
||||
return t('processing.preprocessing.strength.maximum')
|
||||
} else {
|
||||
if (value <= 0.75) return t('processing.preprocessing.strength.subtle')
|
||||
if (value <= 1.25) return t('processing.preprocessing.strength.normal')
|
||||
if (value <= 1.5) return t('processing.preprocessing.strength.strong')
|
||||
return t('processing.preprocessing.strength.maximum')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('border rounded-lg p-4 bg-white', className)}>
|
||||
{/* Header */}
|
||||
@@ -131,14 +145,14 @@ export default function PreprocessingSettings({
|
||||
|
||||
{/* Manual Configuration (shown only when mode is 'manual') */}
|
||||
{mode === 'manual' && (
|
||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg border border-gray-200 space-y-3">
|
||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg border border-gray-200 space-y-4">
|
||||
<h4 className="text-sm font-medium text-gray-700">
|
||||
{t('processing.preprocessing.manualConfig')}
|
||||
</h4>
|
||||
|
||||
{/* Contrast Enhancement */}
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1.5">
|
||||
<div className="space-y-2">
|
||||
<label className="block text-xs font-medium text-gray-600">
|
||||
{t('processing.preprocessing.contrast.label')}
|
||||
</label>
|
||||
<select
|
||||
@@ -157,38 +171,99 @@ export default function PreprocessingSettings({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* Contrast Strength Slider */}
|
||||
{config.contrast !== 'none' && (
|
||||
<div className="pt-1">
|
||||
<div className="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{t('processing.preprocessing.strength.label')}</span>
|
||||
<span className="font-medium text-gray-700">
|
||||
{config.contrast_strength.toFixed(1)} ({getStrengthLabel(config.contrast_strength, 'contrast')})
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="3.0"
|
||||
step="0.1"
|
||||
value={config.contrast_strength}
|
||||
onChange={(e) => handleConfigChange('contrast_strength', parseFloat(e.target.value))}
|
||||
disabled={disabled}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-400 mt-0.5">
|
||||
<span>0.5</span>
|
||||
<span>3.0</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sharpen Toggle */}
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sharpen}
|
||||
onChange={(e) => handleConfigChange('sharpen', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.sharpen')}
|
||||
</span>
|
||||
</label>
|
||||
{/* Sharpen Section */}
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sharpen}
|
||||
onChange={(e) => handleConfigChange('sharpen', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.sharpen')}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{/* Binarize Toggle */}
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.binarize}
|
||||
onChange={(e) => handleConfigChange('binarize', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.binarize')}
|
||||
</span>
|
||||
<span className="text-xs text-orange-600">
|
||||
({t('processing.preprocessing.binarizeWarning')})
|
||||
</span>
|
||||
</label>
|
||||
{/* Sharpen Strength Slider */}
|
||||
{config.sharpen && (
|
||||
<div className="pl-6 pt-1">
|
||||
<div className="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{t('processing.preprocessing.strength.label')}</span>
|
||||
<span className="font-medium text-gray-700">
|
||||
{config.sharpen_strength.toFixed(1)} ({getStrengthLabel(config.sharpen_strength, 'sharpen')})
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="2.0"
|
||||
step="0.1"
|
||||
value={config.sharpen_strength}
|
||||
onChange={(e) => handleConfigChange('sharpen_strength', parseFloat(e.target.value))}
|
||||
disabled={disabled}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-400 mt-0.5">
|
||||
<span>0.5</span>
|
||||
<span>2.0</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Binarize Toggle - Hidden by default, shown only in advanced mode */}
|
||||
<details className="pt-2">
|
||||
<summary className="text-xs text-gray-500 cursor-pointer hover:text-gray-700">
|
||||
{t('processing.preprocessing.advanced')}
|
||||
</summary>
|
||||
<div className="mt-2 pl-2 border-l-2 border-gray-200">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.binarize}
|
||||
onChange={(e) => handleConfigChange('binarize', e.target.checked)}
|
||||
disabled={disabled}
|
||||
className="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">
|
||||
{t('processing.preprocessing.binarize')}
|
||||
</span>
|
||||
<span className="text-xs text-orange-600">
|
||||
({t('processing.preprocessing.binarizeWarning')})
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user