Full Code of fengzhizi715/Monica for AI

main f110bba4c7ad cached
351 files
1.7 MB
445.8k tokens
5 symbols
1 requests
Download .txt
Showing preview only (1,921K chars total). Download the full file or copy to clipboard to get everything.
Repository: fengzhizi715/Monica
Branch: main
Commit: f110bba4c7ad
Files: 351
Total size: 1.7 MB

Directory structure:
gitextract_0i8k_7pw/

├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── FUNCTION.md
├── LICENSE
├── README-EN.md
├── README.md
├── build.gradle.kts
├── compose-desktop.pro
├── config/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── config/
│                               ├── Constants.kt
│                               ├── SystemConstants.kt
│                               ├── category/
│                               │   ├── ConfigCategory.kt
│                               │   ├── ConfigCategoryManager.kt
│                               │   ├── ConfigDefinitions.kt
│                               │   └── ConfigValidator.kt
│                               └── storage/
│                                   ├── ConfigManager.kt
│                                   ├── ConfigStorage.kt
│                                   ├── FileConfigStorage.kt
│                                   ├── PreferencesConfigStorage.kt
│                                   └── RxCacheConfigStorage.kt
├── docs/
│   ├── filter_module_refactor.md
│   ├── layer_render_cache_analysis.md
│   ├── layer_system.md
│   └── layer_system_optimization_roadmap.md
├── domain/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── domain/
│                               ├── ColorCorrectionSettings.kt
│                               ├── ContourDisplaySettings.kt
│                               ├── ContourFilterSettings.kt
│                               ├── DecodedPreviewImage.kt
│                               ├── GeneralSettings.kt
│                               ├── MatchTemplateSettings.kt
│                               ├── MorphologicalOperationSettings.kt
│                               └── NativeImage.kt
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── i18n/
│   ├── QUICK_REFERENCE.md
│   ├── README.md
│   ├── SCRIPT_USAGE_GUIDE.md
│   ├── build.gradle.kts
│   ├── position_check.sh
│   ├── quick_check.sh
│   ├── src/
│   │   ├── main/
│   │   │   ├── kotlin/
│   │   │   │   └── cn/
│   │   │   │       └── netdiscovery/
│   │   │   │           └── monica/
│   │   │   │               └── i18n/
│   │   │   │                   ├── Language.kt
│   │   │   │                   ├── LocalizationManager.kt
│   │   │   │                   └── XmlStringResource.kt
│   │   │   └── resources/
│   │   │       └── strings/
│   │   │           ├── strings_en.xml
│   │   │           └── strings_zh.xml
│   │   └── test/
│   │       └── kotlin/
│   │           └── cn/
│   │               └── netdiscovery/
│   │                   └── monica/
│   │                       └── i18n/
│   │                           └── InternationalizationTest.kt
│   └── string_manager.sh
├── imageprocess/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── imageprocess/
│                               ├── BufferedImages.kt
│                               ├── Colormap.kt
│                               ├── IntIntegralImage.kt
│                               ├── Transformer.kt
│                               ├── domain/
│                               │   ├── ArrayColormap.kt
│                               │   ├── Gradient.kt
│                               │   └── Histogram.kt
│                               ├── filter/
│                               │   ├── BilateralFilter.kt
│                               │   ├── BlockFilter.kt
│                               │   ├── BumpFilter.kt
│                               │   ├── CarveFilter.kt
│                               │   ├── CellularFilter.kt
│                               │   ├── ColorFilter.kt
│                               │   ├── ColorHalftoneFilter.kt
│                               │   ├── ConBriFilter.kt
│                               │   ├── CropFilter.kt
│                               │   ├── CrystallizeFilter.kt
│                               │   ├── DiffuseFilter.kt
│                               │   ├── EmbossFilter.kt
│                               │   ├── EqualizeFilter.kt
│                               │   ├── ExposureFilter.kt
│                               │   ├── GainFilter.kt
│                               │   ├── GammaFilter.kt
│                               │   ├── GaussianNoiseFilter.kt
│                               │   ├── GradientFilter.kt
│                               │   ├── GrayFilter.kt
│                               │   ├── HSBAdjustFilter.kt
│                               │   ├── HighPassFilter.kt
│                               │   ├── InvertFilter.kt
│                               │   ├── MarbleFilter.kt
│                               │   ├── MirrorFilter.kt
│                               │   ├── MosaicFilter.kt
│                               │   ├── NatureFilter.kt
│                               │   ├── OffsetFilter.kt
│                               │   ├── OilPaintFilter.kt
│                               │   ├── PointillizeFilter.kt
│                               │   ├── PosterizeFilter.kt
│                               │   ├── RippleFilter.kt
│                               │   ├── SepiaToneFilter.kt
│                               │   ├── SmearFilter.kt
│                               │   ├── SolarizeFilter.kt
│                               │   ├── SpotlightFilter.kt
│                               │   ├── StrokeAreaFilter.kt
│                               │   ├── SwimFilter.kt
│                               │   ├── VignetteFilter.kt
│                               │   ├── WaterFilter.kt
│                               │   ├── WhiteImageFilter.kt
│                               │   ├── base/
│                               │   │   ├── BaseFilter.kt
│                               │   │   ├── ColorProcessorFilter.kt
│                               │   │   ├── ConvolveFilter.kt
│                               │   │   ├── PointFilter.kt
│                               │   │   ├── TransferFilter.kt
│                               │   │   ├── TransformFilter.kt
│                               │   │   └── WholeImageFilter.kt
│                               │   ├── blur/
│                               │   │   ├── AverageFilter.kt
│                               │   │   ├── BoxBlurFilter.kt
│                               │   │   ├── FastBlur2D.kt
│                               │   │   ├── GaussianFilter.kt
│                               │   │   ├── LensBlurFilter.kt
│                               │   │   ├── MaximumFilter.kt
│                               │   │   ├── MinimumFilter.kt
│                               │   │   ├── MotionFilter.kt
│                               │   │   └── VariableBlurFilter.kt
│                               │   └── sharpen/
│                               │       ├── LaplaceSharpenFilter.kt
│                               │       ├── SharpenFilter.kt
│                               │       └── USMFilter.kt
│                               ├── lut/
│                               │   ├── AutumnLUT.kt
│                               │   ├── BoneLUT.kt
│                               │   ├── CoolLUT.kt
│                               │   ├── HotLUT.kt
│                               │   ├── HsvLUT.kt
│                               │   ├── JetLUT.kt
│                               │   ├── LUT.kt
│                               │   ├── OceanLUT.kt
│                               │   ├── PinkLUT.kt
│                               │   ├── RainbowLUT.kt
│                               │   ├── SpringLUT.kt
│                               │   ├── SummerLUT.kt
│                               │   └── WinterLUT.kt
│                               ├── math/
│                               │   ├── FFT.kt
│                               │   ├── Functions.kt
│                               │   ├── ImageMath.kt
│                               │   └── Noise.kt
│                               └── utils/
│                                   ├── ImageUtils.kt
│                                   └── extension/
│                                       └── BufferedImage+Extensions.kt
├── opencv/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── opencv/
│                               └── ImageProcess.kt
├── resources/
│   ├── common/
│   │   └── filterConfig.json
│   ├── package.json
│   └── web-screenshot.js
├── settings.gradle.kts
└── src/
    ├── jvmMain/
    │   ├── kotlin/
    │   │   ├── Main.kt
    │   │   └── cn/
    │   │       └── netdiscovery/
    │   │           └── monica/
    │   │               ├── config/
    │   │               │   └── Constant.kt
    │   │               ├── di/
    │   │               │   └── appModule.kt
    │   │               ├── exception/
    │   │               │   ├── AppError.kt
    │   │               │   ├── ErrorComposable.kt
    │   │               │   ├── ErrorExtensions.kt
    │   │               │   ├── ErrorHandler.kt
    │   │               │   ├── ErrorManager.kt
    │   │               │   ├── ErrorState.kt
    │   │               │   ├── Errors.kt
    │   │               │   ├── MonicaException.kt
    │   │               │   └── handlers/
    │   │               │       ├── AIServiceErrorHandler.kt
    │   │               │       ├── FileIOErrorHandler.kt
    │   │               │       ├── ImageProcessingErrorHandler.kt
    │   │               │       ├── NetworkErrorHandler.kt
    │   │               │       └── ValidationErrorHandler.kt
    │   │               ├── history/
    │   │               │   ├── EditHistoryCenter.kt
    │   │               │   ├── EditHistoryManager.kt
    │   │               │   ├── HistoryEntry.kt
    │   │               │   └── modules/
    │   │               │       ├── colorcorrection/
    │   │               │       │   └── ColorCorrectionParams.kt
    │   │               │       └── opencv/
    │   │               │           └── CVParams.kt
    │   │               ├── http/
    │   │               │   ├── GsonSerializer.kt
    │   │               │   └── HttpClient.kt
    │   │               ├── llm/
    │   │               │   ├── DeepSeekRequest.kt
    │   │               │   ├── DeepseekClient.kt
    │   │               │   ├── DialogSession.kt
    │   │               │   ├── GeminiClient.kt
    │   │               │   ├── GeminiRequest.kt
    │   │               │   └── LLMServiceManager.kt
    │   │               ├── manager/
    │   │               │   └── OpenCVManager.kt
    │   │               ├── state/
    │   │               │   └── ApplicationState.kt
    │   │               ├── ui/
    │   │               │   ├── controlpanel/
    │   │               │   │   ├── BasicView.kt
    │   │               │   │   ├── ai/
    │   │               │   │   │   ├── AIView.kt
    │   │               │   │   │   ├── AIViewModel.kt
    │   │               │   │   │   ├── experiment/
    │   │               │   │   │   │   ├── BinaryImageView.kt
    │   │               │   │   │   │   ├── CVState.kt
    │   │               │   │   │   │   ├── ContourAnalysisView.kt
    │   │               │   │   │   │   ├── EdgeDetectionView.kt
    │   │               │   │   │   │   ├── ExperimentHome.kt
    │   │               │   │   │   │   ├── ExperimentView.kt
    │   │               │   │   │   │   ├── HistoryView.kt
    │   │               │   │   │   │   ├── ImageDenoisingView.kt
    │   │               │   │   │   │   ├── ImageEnhanceView.kt
    │   │               │   │   │   │   ├── MatchTemplateView.kt
    │   │               │   │   │   │   ├── MorphologicalOperationsView.kt
    │   │               │   │   │   │   ├── NavController.kt
    │   │               │   │   │   │   ├── NavigationHost.kt
    │   │               │   │   │   │   └── viewmodel/
    │   │               │   │   │   │       ├── BinaryImageViewModel.kt
    │   │               │   │   │   │       ├── ContourAnalysisViewModel.kt
    │   │               │   │   │   │       ├── EdgeDetectionViewModel.kt
    │   │               │   │   │   │       ├── HistoryViewModel.kt
    │   │               │   │   │   │       ├── ImageDenoisingViewModel.kt
    │   │               │   │   │   │       ├── ImageEnhanceViewModel.kt
    │   │               │   │   │   │       ├── MatchTemplateViewModel.kt
    │   │               │   │   │   │       └── MorphologicalOperationsViewModel.kt
    │   │               │   │   │   └── faceswap/
    │   │               │   │   │       ├── FaceSwapView.kt
    │   │               │   │   │       └── FaceSwapViewModel.kt
    │   │               │   │   ├── cartoon/
    │   │               │   │   │   ├── CartoonView.kt
    │   │               │   │   │   └── CartoonViewModel.kt
    │   │               │   │   ├── colorcorrection/
    │   │               │   │   │   ├── ColorCorrectionView.kt
    │   │               │   │   │   ├── ColorCorrectionViewModel.kt
    │   │               │   │   │   └── NaturalLanguageDialog.kt
    │   │               │   │   ├── colorpick/
    │   │               │   │   │   ├── ColorPickView.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   ├── ColorData.kt
    │   │               │   │   │   │   ├── ColorNameMap.kt
    │   │               │   │   │   │   └── ColorNameParser.kt
    │   │               │   │   │   ├── utils/
    │   │               │   │   │   │   ├── ColorDetection.kt
    │   │               │   │   │   │   ├── ColorUtils.kt
    │   │               │   │   │   │   └── RoundngUtils.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       ├── ColorDisplay.kt
    │   │               │   │   │       ├── ColorSelectionDrawing.kt
    │   │               │   │   │       └── ImageColorDetector.kt
    │   │               │   │   ├── compression/
    │   │               │   │   │   ├── CompressionActions.kt
    │   │               │   │   │   ├── CompressionAlgorithmDropdown.kt
    │   │               │   │   │   ├── CompressionInputSection.kt
    │   │               │   │   │   ├── CompressionPreview.kt
    │   │               │   │   │   ├── CompressionProgress.kt
    │   │               │   │   │   ├── CompressionSliders.kt
    │   │               │   │   │   ├── CompressionView.kt
    │   │               │   │   │   └── CompressionViewModel.kt
    │   │               │   │   ├── cropimage/
    │   │               │   │   │   ├── CropAgent.kt
    │   │               │   │   │   ├── CropImageSettingView.kt
    │   │               │   │   │   ├── CropImageView.kt
    │   │               │   │   │   ├── CropModifier.kt
    │   │               │   │   │   ├── CropViewModel.kt
    │   │               │   │   │   ├── ImageCropper.kt
    │   │               │   │   │   ├── TouchRegion.kt
    │   │               │   │   │   ├── draw/
    │   │               │   │   │   │   ├── ImageDrawCanvas.kt
    │   │               │   │   │   │   └── Overlay.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   ├── CropAspectRatio.kt
    │   │               │   │   │   │   ├── CropFrame.kt
    │   │               │   │   │   │   ├── CropOutline.kt
    │   │               │   │   │   │   ├── CropOutlineContainer.kt
    │   │               │   │   │   │   └── CropOutlineProperties.kt
    │   │               │   │   │   ├── setting/
    │   │               │   │   │   │   ├── CropDefaults.kt
    │   │               │   │   │   │   ├── CropFrameFactory.kt
    │   │               │   │   │   │   └── Paths.kt
    │   │               │   │   │   ├── state/
    │   │               │   │   │   │   ├── CropState.kt
    │   │               │   │   │   │   ├── CropStateImpl.kt
    │   │               │   │   │   │   ├── DynamicCropState.kt
    │   │               │   │   │   │   ├── StaticCropState.kt
    │   │               │   │   │   │   └── TransformState.kt
    │   │               │   │   │   └── utils/
    │   │               │   │   │       ├── DrawScopeUtils.kt
    │   │               │   │   │       ├── ShapeUtils.kt
    │   │               │   │   │       └── ZoomUtils.kt
    │   │               │   │   ├── doodle/
    │   │               │   │   │   ├── DoodleView.kt
    │   │               │   │   │   ├── DoodleViewModel.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   └── PathProperties.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       └── PropertiesMenuDialog.kt
    │   │               │   │   ├── filter/
    │   │               │   │   │   ├── FilterView.kt
    │   │               │   │   │   ├── viewmodel/
    │   │               │   │   │   │   └── FilterViewModel.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       ├── FilterAdjustmentPanel.kt
    │   │               │   │   │       ├── FilterListPanel.kt
    │   │               │   │   │       ├── FilterParamDefaults.kt
    │   │               │   │   │       ├── FilterParamMeta.kt
    │   │               │   │   │       ├── FilterPreviewArea.kt
    │   │               │   │   │       └── FilterTopAppBar.kt
    │   │               │   │   ├── generategif/
    │   │               │   │   │   ├── GenerateGifView.kt
    │   │               │   │   │   └── GenerateGifViewModel.kt
    │   │               │   │   ├── shapedrawing/
    │   │               │   │   │   ├── CoordinateSystem.kt
    │   │               │   │   │   ├── EditorController.kt
    │   │               │   │   │   ├── ShapeDrawingView.kt
    │   │               │   │   │   ├── ShapeDrawingViewModel.kt
    │   │               │   │   │   ├── animation/
    │   │               │   │   │   │   └── ShapeAnimationManager.kt
    │   │               │   │   │   ├── coordinate/
    │   │               │   │   │   │   └── CoordinateConverter.kt
    │   │               │   │   │   ├── geometry/
    │   │               │   │   │   │   ├── CanvasDrawer.kt
    │   │               │   │   │   │   ├── Drawer.kt
    │   │               │   │   │   │   └── Style.kt
    │   │               │   │   │   ├── handler/
    │   │               │   │   │   │   └── ShapeDrawingEventHandler.kt
    │   │               │   │   │   ├── helper/
    │   │               │   │   │   │   └── SpecialLayerHelper.kt
    │   │               │   │   │   ├── layer/
    │   │               │   │   │   │   ├── ImageLayer.kt
    │   │               │   │   │   │   ├── Layer.kt
    │   │               │   │   │   │   ├── LayerManager.kt
    │   │               │   │   │   │   ├── LayerRenderer.kt
    │   │               │   │   │   │   ├── ShapeLayer.kt
    │   │               │   │   │   │   └── SpecialLayerHelper.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   ├── Shape.kt
    │   │               │   │   │   │   └── ShapeProperties.kt
    │   │               │   │   │   ├── state/
    │   │               │   │   │   │   └── ShapeDrawingState.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       ├── CanvasView.kt
    │   │               │   │   │       ├── DraggableTextField.kt
    │   │               │   │   │       ├── ImageLayerControlRenderer.kt
    │   │               │   │   │       ├── LayerPanel.kt
    │   │               │   │   │       ├── ShapeDrawingPropertiesMenuDialog.kt
    │   │               │   │   │       └── TextDrawer.kt
    │   │               │   │   └── webscreenshot/
    │   │               │   │       ├── WebScreenshotView.kt
    │   │               │   │       └── WebScreenshotViewModel.kt
    │   │               │   ├── i18n/
    │   │               │   │   └── ComposeI18n.kt
    │   │               │   ├── main/
    │   │               │   │   ├── ContentPanel.kt
    │   │               │   │   ├── Dialogs.kt
    │   │               │   │   ├── GeneralSettingsDialog.kt
    │   │               │   │   ├── MainView.kt
    │   │               │   │   ├── MainViewModel.kt
    │   │               │   │   └── SidebarView.kt
    │   │               │   ├── preview/
    │   │               │   │   ├── PreviewViewModel.kt
    │   │               │   │   └── PreviewViewt.kt
    │   │               │   ├── screenshot/
    │   │               │   │   └── SwingScreenshotAreaSelector.kt
    │   │               │   ├── showimage/
    │   │               │   │   └── ShowImageView.kt
    │   │               │   ├── theme/
    │   │               │   │   ├── ColorTheme.kt
    │   │               │   │   └── ThemeManager.kt
    │   │               │   └── widget/
    │   │               │       ├── Buttons.kt
    │   │               │       ├── Checkboxs.kt
    │   │               │       ├── Divider.kt
    │   │               │       ├── LazyRow.kt
    │   │               │       ├── PageLifecycle.kt
    │   │               │       ├── RightSideMenuBar.kt
    │   │               │       ├── TextFields.kt
    │   │               │       ├── ThreeBallLoading.kt
    │   │               │       ├── Title.kt
    │   │               │       ├── Toasts.kt
    │   │               │       ├── color/
    │   │               │       │   ├── ColorSelection.kt
    │   │               │       │   └── ColorSelectionDialog.kt
    │   │               │       ├── image/
    │   │               │       │   ├── ImageContentScaleUtil.kt
    │   │               │       │   ├── ImageScope.kt
    │   │               │       │   ├── ImageSizeCalculator.kt
    │   │               │       │   ├── ImageWithConstraints.kt
    │   │               │       │   ├── ImageWithThumbnail.kt
    │   │               │       │   └── gesture/
    │   │               │       │       ├── AwaitDragMotionModifier.kt
    │   │               │       │       ├── AwaitPointerMontionEvent.kt
    │   │               │       │       ├── MotionEvent.kt
    │   │               │       │       ├── PointerMotionModify.kt
    │   │               │       │       └── TransformGestures.kt
    │   │               │       └── properties/
    │   │               │           └── ExposedSelectionMenu.kt
    │   │               └── utils/
    │   │                   ├── AppDirs.kt
    │   │                   ├── ButtonUtils.kt
    │   │                   ├── DebugUtils.kt
    │   │                   ├── FileChoose.kt
    │   │                   ├── IOUtils.kt
    │   │                   ├── ImageCompressionUtils.kt
    │   │                   ├── ImageFormatDetector.kt
    │   │                   ├── ImageUtils.kt
    │   │                   ├── LogHomeProperty.kt
    │   │                   ├── LogUtils.kt
    │   │                   ├── ScreenshotUtils.kt
    │   │                   ├── TextUtils.kt
    │   │                   ├── TimeUtils.kt
    │   │                   ├── Typealiases.kt
    │   │                   ├── Validation.kt
    │   │                   ├── WebScreenshotUtils.kt
    │   │                   └── extensions/
    │   │                       ├── Any+Extensions.kt
    │   │                       ├── Coroutine+Extensions.kt
    │   │                       ├── DrawScope+Extensions.kt
    │   │                       ├── Number+Extensions.kt
    │   │                       └── String+Extensions.kt
    │   └── resources/
    │       └── logback.xml
    └── jvmTest/
        └── kotlin/
            └── cn/
                └── netdiscovery/
                    └── monica/
                        └── editor/
                            └── layer/
                                ├── ExportManagerTest.kt
                                └── LayerManagerTest.kt

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto


================================================
FILE: .gitignore
================================================
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
bin/
config/bin/
domain/bin/
i18n/bin/
imageprocess/bin/
opencv/bin/

### IntelliJ IDEA ###
.idea/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store

.idea
.kotlin

# cache
rxcache/

# log
log/

# spec
.cursor/
.specify

================================================
FILE: CHANGELOG.md
================================================
Monica
===

Version 1.1.4
---
2025-9-25
* 增加使用 Gemini 实现自然语言实现调色的功能
* 增加主题的功能,Monica 可以切换主题
* 增加国际化,支持英文版本
* 重构 Monica UI 的首页
* 重构图像绘制形状模块
* 重构涂鸦模块

Version 1.1.3
---
2025-8-4
* 增加使用自然语言实现调色的功能
* 增加 OpenCV 调参过程的记录

Version 1.1.2
---
2025-7-28
* 优化图像调色的代码(使用图像金字塔优化raw、heif文件的加载和调色)

Version 1.1.1
---
2025-7-15
* 优化 jni 的代码
* 优化图像调色的代码(使用并行 + LUT)

Version 1.1.0
---
2025-6-25
* 在 macOS 上从零构建 libheif + OpenCV 图像处理库,而不是依赖 brew 安装的库
* 优化代码

Version 1.0.9
---
2025-6-6
* 增加多种格式的导入导出
* 修复 macOS 打包安装失败的 bug

Version 1.0.8
---
2025-4-20
* 修复保存 png 图像出错的 bug

Version 1.0.7
---
2025-4-18
* 优化图片的加载过程
* 支持使用 GPU 来推理(前提是需要支持CUDA)
* 增加生成多种风格的漫画

Version 1.0.6
---
2025-4-8
* 滤镜数量增加到50多款
* 模型的调用从调用本地算法迁移到通过 http 调用算法服务,减少软件对模型文件的依赖。
* 增加软件的通用设置
* Kotlin 版本升级到 2.1.0

Version 1.0.5
---
2025-3-6
* 增加将多张图片生成 gif 的功能
* 优化滤镜相关的配置

Version 1.0.4
---
2025-1-23
* 完善对 CV 算法快速调参的模块

Version 1.0.3
---
2024-11-27
* 修复图像调色的 bug

Version 1.0.2
---
2024-11-26
* 增加形状绘制和添加文字
* 增加图像调色
* 完善 Monica 常用的组件

Version 1.0.1
---
2024-11-3
* 增加对 CV 算法快速调参的模块
* 完善 Monica 常用的组件

Version 1.0.0
---
2024-9-18
* Kotlin 版本升到 2.0.20
* 增加图像错切
* 增加多种图像增强的算法
* 增加深度学习相关的功能(人脸检测、生成素描画、替换人脸)

Version 0.2.6
---
2024-7-18
* 在 MacOS(只针对Intel 芯片)增加多种图像增强的算法,通过 OpenCV C++ 实现

Version 0.2.5
---
2024-7-13
* 增加 NatureFilter 滤镜
* 增加 logback 作为日志框架

Version 0.2.4
---
2024-6-30
* 增加 FastBlur2D 滤镜
* 增加图像裁剪的属性

Version 0.2.3
---
2024-6-17
* 增加图片取色功能
* 增加 ColorFilter
* 优化架构

Version 0.2.2
---
2024-6-13
* 完善图像的裁剪功能

Version 0.2.1
---
2024-6-9
* 优化裁剪的 UI
* 优化滤镜相关的架构

Version 0.2.0
---
2024-5-29
* 增加图像的裁剪功能
* 增加 VignetteFilter
* 增加 toast 提示

Version 0.1.5
---
2024-5-25
* 升级 koin 版本
* 优化图像的涂鸦功能
* 增加 StrokeAreaFilter

Version 0.1.4
---
2024-5-24
* 增加图像的涂鸦功能

Version 0.1.3
---
2024-5-11
* 增加图像的 resize 功能
* 增加带 tooltip 的按钮

Version 0.1.2
---
2024-5-9
* 增加 EmbossFilter、OilPaintFilter
* 修复某种情况下无法保存图像的 bug

Version 0.1.1
---
2024-5-8
* 增加图像的 flip、rotate 功能
* 引入 koin 作为依赖注入的容器

Version 0.1.0
---
2024-5-6
* 提供加载本地图片、网络图片。
* 对图片局部模糊、打马赛克。
* 调整图片的饱和度、色相、亮度。
* 提供 20 款滤镜,大多数滤镜也可以单独调整参数。
* 对修改的图像进行保存。
* 放大、缩小图像。

================================================
FILE: FUNCTION.md
================================================
## 2.1 基础功能
加载完图像后,就可以对图像进行各种编辑和操作
![](images/1-1.png)

Monica 基础功能的按钮,都带有 tooltips ,例如这个涂鸦功能
![](images/1-2.png)

点击按钮就可以进入涂鸦界面,对图像进行随意的涂鸦。
![](images/1-3.png)

由于 Monica 是一款桌面软件,画笔由鼠标进行控制。画笔默认是黑色的,可以随着鼠标的移动而进行绘制曲线。Monica 支持选择画笔的颜色,以及选择画笔的粗细。
![](images/1-4.png)
![](images/1-5.png)

涂鸦完之后,记得保存图片,这样回到主界面之后才真正的保存结果了。
![](images/1-6.png)

在基础功能里,还有一个比较有意思的功能,对图像取色
![](images/1-7.png)

这个功能通过点击图像中的位置,获取颜色相关的信息,包括 HEX 颜色代码值、RGB 值、HSL 值和 HSV 值。
![](images/1-8.png)
![](images/1-9.png)

## 2.2 裁剪
基础功能有个比较强大的功能——裁剪 ,通过点击带提示的裁剪按钮
![](images/2-1.png)

可以进入图像裁剪的界面
![](images/2-2.png)

用户可以基于九宫格的选框,对图像进行裁剪。
![](images/2-3.png)
![](images/2-4.png)

裁剪完之后,会在主界面显示截取之后的图像。
![](images/2-5.png)

当然,这只是最基本的裁剪功能,Monica 可以通过设置裁剪属性支持多种形式的裁剪。
![](images/2-6.png)

下面,我们以正六边形为裁剪框来裁剪图像
![](images/2-7.png)
![](images/2-8.png)

接下来,还可以以爱心为裁剪框来裁剪图像
![](images/2-9.png)
![](images/2-10.png)


## 2.3 图像绘制
形状绘制的入口
![](images/3-1.png)

绘制形状的页面
![](images/3-2.png)

Monica 提供了图像上的任意位置绘制各种图形的功能,以及修改图形的属性比如图像的颜色、透明度、是否填充、边框类型。
![](images/3-3.png)

保存图像
![](images/3-4.png)

## 2.4 图像调色
Monica 支持调节图像的对比度、色调、饱和度、亮度、色温等,从而帮助大家调整图像的色彩。

图像调色的入口
![](images/4-1.png)

图像调色的界面
![](images/4-2.png)

支持拖动调节各个参数
![](images/4-3.png)

![](images/4-4.png)

保存图像
![](images/4-5.png)

Monica 还支持通过 LLM 进行调色,主要是 deepseek、 gemini。用户每次输入一句自然语言指令,比如“肤色偏黄,冷一点”,就可以进行调色。也支持多轮对话和切换不同的大模型。

![](images/4-6.png)

![](images/4-7.png)

![](images/4-8.png)

![](images/4-9.png)

![](images/4-10.png)

## 2.5 滤镜
Monica 支持多达 50 多款滤镜,大多数可以自行调整参数。
![](images/5-1.png)

如果需要修改滤镜的默认参数,可以直接修改。
![](images/5-2.png)

![](images/5-3.png)

![](images/5-4.png)

![](images/5-5.png)

![](images/5-6.png)

![](images/5-7.png)

![](images/5-8.png)

![](images/5-9.png)

![](images/5-10.png)

各种滤镜效果可以不断叠加,也可以跟其他功能一起使用。


## 2.6 深度学习的算法
在 AI 实验室有一些比较有意思的算法,比如人脸检测、生成素描画、人脸替换

人脸检测包括:人脸、年龄、性别检测
![](images/6-1.png)

生成素描画的效果
![](images/6-2.png)

人脸替换
![](images/6-3.png)

"人脸替换"需要一张源图和加载一张目标图片。
![](images/6-4.png)
![](images/6-5.png)
![](images/6-6.png)

"人脸替换"也支持将目标图中所有的人脸进行替换。
![](images/6-7.png)
只需要设置一下替换 target 中人脸的数量即可。
![](images/6-8.png)
就可以完成目标图中所有的人脸替换。
![](images/6-9.png)

## 2.7 快速验证 OpenCV 算法的功能

快速验证 OpenCV 算法的功能,更像是一个简单的快速调试开发工具,我做它的目的是为了快速验证一些算法,未来也不排除会把这个模块独立出来。

下面是该模块的入口
![](images/7-1.png)

以及该模块的首页
![](images/7-2.png)

================================================
FILE: LICENSE
================================================
Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README-EN.md
================================================
**Monica** is a cross-platform desktop image editor.
It supports a wide range of image formats (including camera RAW), integrates both traditional image processing and deep learning–based image enhancement, and provides an extensible, developer-friendly editing experience.

# 🧪 Tech Stack

* **UI Framework**: Kotlin Compose Multiplatform (Desktop)
* **Image Processing**: OpenCV
* **Deep Learning Inference**: ONNX Runtime
* **Backend Languages**: Kotlin / C++
* **Build Tools**: Gradle / CMake

# ✨ Features
## 📷 Image Editing

* Import: JPG, PNG, WebP, SVG, HDR, HEIC
* Import camera RAW files: CR2, CR3, etc.
* Export: JPG, PNG, WebP
* Zoom and preview
* Local blur & mosaic
* Freehand drawing, shapes, and text annotations
* Color picker
* Geometric transforms: flip, rotate, scale, shear
* Cropping with multiple shapes
* Adjustments: contrast, hue, saturation, brightness, temperature, highlights, shadows
* 50+ adjustable filters
* Multi-image → GIF creation
* Quick validation of OpenCV algorithms with parameter tuning

## 🤖 AI-powered Enhancements

* Face detection (face, gender, age)
* Sketch generation from photos
* Face replacement
* Cartoonization with multiple styles

# 📦 Installation & Usage
## Run from Source

Use IntelliJ IDEA / IntelliJ IDEA CE

```bash
git clone https://github.com/fengzhizi715/Monica.git
cd Monica
./gradlew run
```

## Packaging

Recommended packaging command:

```bash
./gradlew packageCurrentOsWithBundledWebRuntime
```

Notes:

* Local development defaults to `isProVersion=false`
* Packaging tasks automatically switch to `isProVersion=true`
* macOS output: `build/output/main/dmg/`
* Windows output: `build/output/main/exe/`
* Linux output: `build/output/main/rpm/`

If you want to run platform-specific tasks directly:

```bash
./gradlew packageDmg
./gradlew packageExe
./gradlew packageRpm
```

## 🍎 macOS Packages

### Intel Chip:
Monica-x64-1.1.4.dmg

Download Link: https://pan.baidu.com/s/1ZS2e8krIh_kGUUEogMknrg?pwd=eyx7

### Apple Silicon (M Series):
Monica-arm64-1.1.4.dmg

Download Link: https://pan.baidu.com/s/1JJwT_UNFrQa-tUsAYywqkA?pwd=mngu

## 🖥 Windows Package

Monica-1.0.9.exe (latest version will be provided later, no Windows machine available now)

Download Link: https://pan.baidu.com/s/1jL0bL17Omxtc2rqOBn9yWg?pwd=5dii

## 🐧 CentOS Package

Coming soon

# 📸 Screenshots
## ✨ New UI Preview

Support for **English UI + Multiple Themes**

English UI examples:

![](images/screenshot-en1.png)

![](images/screenshot-en2.png)

Theme switching:
![](images/ui-theme-settings.png)

Dark Theme:
![](images/ui-theme-dark.png)

Purple Theme:
![](images/ui-theme-purple.png)

## 📷 Classic Features

![](images/screenshot.png)

![](images/screenshot-version.png)

![](images/4-2.png)

![](images/5-2.png)

![](images/7-2.png)

More screenshots 👉 [Feature Overview](FUNCTION.md)

Articles 👉 [Juejin Column](https://juejin.cn/column/7396157773312065574)

# 📁 CV & AI Services
## ⚙️ CV Algorithms

Code repo: https://github.com/fengzhizi715/MonicaImageProcess

Currently, prebuilt algorithm libraries are available for macOS and Windows. Kotlin calls them via JNI.

|Library Name	                        | Version | 	Description	  | Notes                           |
|---------------------------------------|-------|---------------------|---------------------------------|
|libMonicaImageProcess.dylib	|0.2.3	|Prebuilt for macOS	| Built with CLion |
|libopencv_world.4.10.0.dylib	|–	|OpenCV 4.10.0 prebuilt for macOS	|Built with CMake |
|MonicaImageProcess.dll	        | 0.2.1	|Prebuilt for Windows, depends on opencv_world481.dll|	Built with Visual Studio 2022 |
|opencv_world481.dll	         | –	|OpenCV 4.8.1 prebuilt for Windows	| Built with Visual Studio 2022 |

## ☁️ Deep Learning Services

Monica communicates with deep learning inference services via HTTP.
You need to set the `Algorithm Service URL` in **General Settings**.

Source code & models 👉 https://github.com/fengzhizi715/MonicaImageProcessHttpServer

> No online deployment provided. Feel free to build and run locally.

# 💻 Roadmap

* - [x] Multi-format import/export
* - [x] Core image editing features
* - [x] AI module integration
* - [ ] Plugin system support
* - [ ] More AI features (face retouching, background removal, style transfer, etc.)

Upcoming TODO:

* Unified error handling
* Improved configuration management
* Enhanced cropping tools
* Face retouching
* Image compression
* Upgrade Kotlin Compose Desktop & third-party libraries

# 🤝 Contributing

Contributions of all kinds are welcome: new features, bug fixes, docs, or feedback.

# 📄 License

Apache License 2.0

# 📝 Changelog

See [CHANGELOG](CHANGELOG.md)

# 📬 Contact

WeChat: fengzhizi715

Email: fengzhizi715@126.com

# 📈 Star History

[![Star History Chart](https://api.star-history.com/svg?repos=fengzhizi715/Monica&type=Date)](https://star-history.com/#fengzhizi715/Monica&Date)


================================================
FILE: README.md
================================================
**Monica** 是一款跨平台的桌面图像编辑软件。它不仅支持多种图像格式(包括相机 RAW),还集成了传统图像处理和基于深度学习的图像增强功能,提供可扩展、可二次开发的图像编辑体验。

# 🧪 技术栈
* **UI 框架**:Kotlin Compose Multiplatform (Desktop)
* **图像处理**:OpenCV
* **深度学习推理**:ONNX Runtime
* **后端语言**:Kotlin / C++
* **构建工具**:Gradle / CMake

# ✨ 功能列表
## 📷 图像处理功能
* 支持导入:JPG、PNG、WebP、SVG、HDR、HEIC
* 支持相机 RAW 文件导入:CR2、CR3 等
* 支持导出:JPG、PNG、WebP
* 图像放大预览
* 局部模糊、马赛克处理
* 涂鸦、绘制形状、添加文字
* 图像取色
* 图像几何变换:翻转、旋转、缩放、错切
* 支持各种形状的裁剪
* 调整参数:对比度、色调、饱和度、亮度、色温、高光、阴影
* 50+ 可调节滤镜
* 多图合成 GIF
* 快速验证 OpenCV 算法,支持简单算法的调参

## 🤖 深度学习增强功能
* 人脸检测(人脸、性别、年龄)
* 图像生成素描画
* 替换人脸
* 多种风格的漫画生成

# 📦 安装与运行

## 从源码运行
使用 IntelliJ IDEA / IntelliJ IDEA CE

```bash
git clone https://github.com/fengzhizi715/Monica.git
cd Monica
./gradlew run
```

## 打包

当前推荐直接使用统一打包命令:

```bash
./gradlew packageCurrentOsWithBundledWebRuntime
```

说明:

* 本地开发默认使用 `isProVersion=false`
* 打包任务会自动切换到 `isProVersion=true`
* macOS 产物默认输出到 `build/output/main/dmg/`
* Windows 产物默认输出到 `build/output/main/exe/`
* Linux 产物默认输出到 `build/output/main/rpm/`

如果需要直接调用平台任务,也可以使用:

```bash
./gradlew packageDmg
./gradlew packageExe
./gradlew packageRpm
```

## 🍎 macOS 安装包
### Intel 芯片:
Monica-x64-1.1.4.dmg

链接: https://pan.baidu.com/s/1ZS2e8krIh_kGUUEogMknrg?pwd=eyx7

### M 芯片:
Monica-arm64-1.1.4.dmg

链接: https://pan.baidu.com/s/1JJwT_UNFrQa-tUsAYywqkA?pwd=mngu

## 🖥 Windows 安装包
Monica-1.0.9.exe (最近没有 windows 电脑,稍后提供最新的版本)

链接: https://pan.baidu.com/s/1jL0bL17Omxtc2rqOBn9yWg?pwd=5dii

## 🐧 CentOS 安装包
稍后提供

# 📸 项目截图

## ✨ UI 新版预览图
支持 **英文版 UI + 多主题颜色切换**

英文版界面示例
![](images/screenshot-en1.png)

![](images/screenshot-en2.png)

主题切换
![](images/ui-theme-settings.png)

深色主题
![](images/ui-theme-dark.png)

紫色主题
![](images/ui-theme-purple.png)

## 📷 经典功能界面

![](images/screenshot.png)

![](images/screenshot-version.png)

![](images/4-2.png)

![](images/5-2.png)

![](images/7-2.png)


更多截图 👉 [详细功能介绍](FUNCTION.md)

专栏文章 👉 [掘金专栏](https://juejin.cn/column/7396157773312065574)

# 📁 CV 算法 && 深度学习的服务

## ⚙️ CV 算法

CV 算法的地址:
https://github.com/fengzhizi715/MonicaImageProcess

目前在 macOS、Windows 环境下编译好了相关的算法库,Kotlin 通过 jni 来调用该算法库。


| 库名                                   | 版本号   | 描述                                                 | 备注                             |
|---------------------------------------|-------|------------------------------------------------------|---------------------------------|
| libMonicaImageProcess.dylib           | 0.2.3 | macOS 下编译好的算法库                                  | 使用 CLion 编译                  |
| libopencv_world.4.10.0.dylib          |       | macOS 下基于 OpenCV 4.10.0 源码编译的 OpenCV 库          | 使用 cmake 编译                  |
| MonicaImageProcess.dll                | 0.2.1 | Windows 下编译好的算法库需要依赖 opencv_world481.dll      | 使用 Visual Studio 2022 编译     |
| opencv_world481.dll                   |       | Windows 下基于 OpenCV 4.8.1 源码编译的 OpenCV 库         | 使用 Visual Studio 2022 编译     |


## ☁️ 深度学习的服务

Monica 通过 HTTP 调用深度学习推理服务。需在 **通用设置** 中配置 `算法服务 URL`。

源码与模型 👉 https://github.com/fengzhizi715/MonicaImageProcessHttpServer

> 未部署线上服务,感兴趣可自行编译和部署


# 💻 项目计划:
* - [x] 多格式导入导出支持
* - [x] 图像基础编辑功能
* - [x] 深度学习模块集成
* - [ ] 支持插件机制
* - [ ] 添加更多 AI 功能(如人脸美颜、去背景、风格化等)

近期的 TODO : 

* 优化滤镜模块,使用 LLM 实现自然语言使用滤镜
* 完善配置管理
* 优化图像裁剪的功能
* 增加人脸美颜的功能
* 增加插件机制
* 升级 Kotlin Compose desktop、第三方库的版本


# 🤝 贡献方式
欢迎任何形式的贡献,包括但不限于功能开发、Bug 修复、文档完善和使用反馈。


# 📄 开源协议
本项目基于 Apache License 2.0 开源。


# 📝 更新日志

请查看 [CHANGELOG](CHANGELOG.md) 文件


# 📬 联系方式:

wechat:fengzhizi715

Email:fengzhizi715@126.com


# 📈 Star History

[![Star History Chart](https://api.star-history.com/svg?repos=fengzhizi715/Monica&type=Date)](https://star-history.com/#fengzhizi715/Monica&Date)


================================================
FILE: build.gradle.kts
================================================
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.security.MessageDigest

plugins {
    kotlin("multiplatform")
    id("org.jetbrains.compose")
    id("org.jetbrains.kotlin.plugin.compose") version "2.1.0"
}


group = "cn.netdiscovery.monica"
version = "${rootProject.extra["app.version"]}"

val mOutputDir = project.buildDir.resolve("output")

repositories {
    google()
    mavenCentral()
    maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
    maven( "https://jitpack.io" )
}

val osName = System.getProperty("os.name")
val targetOs = when {
    osName == "Mac OS X" -> "macos"
    osName.startsWith("Win") -> "windows"
    osName.startsWith("Linux") -> "linux"
    else -> error("Unsupported OS: $osName")
}

val osArch = System.getProperty("os.arch")
var targetArch = when (osArch) {
    "x86_64", "amd64" -> "x64"
    "aarch64" -> "arm64"
    else -> error("Unsupported arch: $osArch")
}

val skikoVersion = "0.8.4"
val target = "${targetOs}-${targetArch}"
val resourcesRootDir = project.layout.projectDirectory.dir("resources").asFile
val bundledNodeVersion = providers.gradleProperty("bundledNodeVersion").orElse("20.18.0")
val stagedWebRuntimeDir = layout.buildDirectory.dir("generated/web-screenshot-runtime/$target")
val packagedWebRuntimeDir = layout.buildDirectory.dir("generated/web-screenshot-payload/$target")
val packagedWebRuntimeZip = layout.buildDirectory.file("generated/web-screenshot-payload/$target/runtime.zip")
val packagedWebRuntimeResourceDir = File(resourcesRootDir, "common/web-screenshot-runtime/$target")
val packagedWebRuntimeResourceZip = File(packagedWebRuntimeResourceDir, "runtime.zip")

fun getBundledNodeDistPlatform(): String = when (targetOs) {
    "macos" -> if (targetArch == "arm64") "darwin-arm64" else "darwin-x64"
    "windows" -> if (targetArch == "arm64") "win-arm64" else "win-x64"
    "linux" -> if (targetArch == "arm64") "linux-arm64" else "linux-x64"
    else -> error("Unsupported target OS for bundled Node.js: $targetOs")
}

fun getBundledNodeArchiveExtension(): String = if (targetOs == "windows") "zip" else "tar.gz"

fun getBundledNodeExtractedDirName(version: String): String =
    "node-v$version-${getBundledNodeDistPlatform()}"

fun sha256(file: File): String {
    val digest = MessageDigest.getInstance("SHA-256")
    file.inputStream().use { input ->
        val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
        while (true) {
            val read = input.read(buffer)
            if (read <= 0) break
            digest.update(buffer, 0, read)
        }
    }
    return digest.digest().joinToString("") { "%02x".format(it) }
}

fun findWebScreenshotRuntimeRoot(baseDir: File): File? {
    val candidates = listOf(
        baseDir,
        File(baseDir, "common")
    )

    return candidates.firstOrNull { candidate ->
        File(candidate, "web-screenshot.js").exists()
    }
}

fun findBundledNodeExecutable(baseDir: File): File? {
    val candidates = if (targetOs == "windows") {
        listOf(
            File(baseDir, "$target/node/node.exe"),
            File(baseDir, "$target/node.exe")
        )
    } else {
        listOf(
            File(baseDir, "$target/node/bin/node"),
            File(baseDir, "$target/node/node"),
            File(baseDir, "$target/bin/node")
        )
    }

    return candidates.firstOrNull { it.exists() }
}

fun findBundledNodeExecutableInRuntime(runtimeRoot: File): File? {
    val candidates = if (targetOs == "windows") {
        listOf(
            File(runtimeRoot, "node/node.exe"),
            File(runtimeRoot, "node.exe")
        )
    } else {
        listOf(
            File(runtimeRoot, "node/bin/node"),
            File(runtimeRoot, "node/node"),
            File(runtimeRoot, "bin/node")
        )
    }
    return candidates.firstOrNull { it.exists() }
}

fun findPlaywrightBrowsersInRuntime(runtimeRoot: File): File? {
    val candidates = listOf(
        File(runtimeRoot, "node_modules/playwright-core/.local-browsers"),
        File(runtimeRoot, "ms-playwright")
    )
    return candidates.firstOrNull { it.exists() }
}

fun hasInstalledPlaywrightBrowsersInRuntime(runtimeRoot: File): Boolean {
    val browsersDir = findPlaywrightBrowsersInRuntime(runtimeRoot) ?: return false
    val entries = browsersDir.listFiles().orEmpty().filter { !it.name.startsWith(".") }
    val hasChromium = entries.any { it.name.startsWith("chromium-") }
    val hasHeadlessShell = entries.any { it.name.startsWith("chromium_headless_shell-") }
    val hasFfmpeg = entries.any { it.name.startsWith("ffmpeg-") }
    return hasChromium && hasHeadlessShell && hasFfmpeg
}

fun getNpmCliScript(nodeDir: File): File {
    val script = File(nodeDir, "lib/node_modules/npm/bin/npm-cli.js")
    if (!script.exists()) {
        throw GradleException("Bundled npm CLI not found at ${script.absolutePath}")
    }
    return script
}

fun getNpxCliScript(nodeDir: File): File {
    val directScript = File(nodeDir, "lib/node_modules/npm/bin/npx-cli.js")
    if (directScript.exists()) {
        return directScript
    }

    val fallbackScript = File(nodeDir, "lib/node_modules/npm/bin/npm-cli.js")
    if (fallbackScript.exists()) {
        return fallbackScript
    }

    throw GradleException("Bundled npx/npm CLI not found under ${nodeDir.absolutePath}")
}

fun getBundledNodeCommand(nodeDir: File): File {
    val executable = if (targetOs == "windows") {
        File(nodeDir, "node.exe")
    } else {
        File(nodeDir, "bin/node")
    }

    if (!executable.exists()) {
        throw GradleException("Bundled Node.js executable not found at ${executable.absolutePath}")
    }

    return executable
}

kotlin {
    jvm {
        withJava()
    }
    sourceSets {
        val jvmMain by getting {
            dependencies {
                implementation(compose.desktop.currentOs)
                implementation(project(":domain"))
                implementation(project(":config"))
                implementation(project(":imageprocess"))
                implementation(project(":opencv"))
                implementation(project(":i18n"))

                implementation ("org.jetbrains.kotlin:kotlin-reflect")

                // skiko
                implementation("org.jetbrains.skiko:skiko-awt-runtime-$target:$skikoVersion")

                // 缓存
                implementation("com.github.fengzhizi715.RxCache:core:${rootProject.extra["rxcache"]}")
                implementation("com.github.fengzhizi715.RxCache:okio:${rootProject.extra["rxcache"]}")
                implementation("com.github.fengzhizi715.RxCache:extension:${rootProject.extra["rxcache"]}")

                // di
                implementation("io.insert-koin:koin-compose:${rootProject.extra["koin.compose"]}")

                // color math
                implementation("com.github.ajalt.colormath:colormath-ext-jetpack-compose:${rootProject.extra["colormath"]}")

                // coroutines utils
                implementation ("com.github.fengzhizi715.Kotlin-Coroutines-Utils:common:${rootProject.extra["coroutines.utils"]}")
                // 为 Desktop/Swing 提供 Dispatchers.Main(绑定到 EDT)
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:${rootProject.extra["kotlinx.coroutines.core.version"]}")

                // log config
                implementation("ch.qos.logback:logback-classic:${rootProject.extra["logback"]}")
                implementation("ch.qos.logback:logback-core:${rootProject.extra["logback"]}")
                implementation("ch.qos.logback:logback-access:${rootProject.extra["logback"]}")

                // okhttp-extension
                implementation("com.github.fengzhizi715.okhttp-extension:core:1.3.2")
                implementation("com.github.fengzhizi715.okhttp-logging-interceptor:core:v1.1.4")
                implementation ("com.squareup.okhttp3:okhttp:4.10.0")
                implementation ("com.google.code.gson:gson:2.10.1")
                implementation ("org.json:json:20240303")

                // generate gif
                implementation ("com.madgag:animated-gif-lib:1.4")

                // sqlite
                implementation("org.xerial:sqlite-jdbc:3.50.3.0")
            }
        }
        val jvmTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
    }
}

val verifyBundledWebScreenshotRuntime by tasks.registering {
    group = "verification"
    description = "Verify offline web screenshot payload resources for desktop packaging."

    doLast {
        val runtimeRoot = findWebScreenshotRuntimeRoot(resourcesRootDir)
            ?: throw GradleException(
                "Missing web screenshot runtime root. Expected web-screenshot.js under " +
                    "${resourcesRootDir.absolutePath} or ${File(resourcesRootDir, "common").absolutePath}."
            )

        val missing = mutableListOf<String>()

        if (!File(runtimeRoot, "package.json").exists()) {
            missing += "${runtimeRoot.absolutePath}/package.json"
        }
        val payloadZip = packagedWebRuntimeResourceZip
        if (!payloadZip.exists()) {
            missing += payloadZip.absolutePath
        }

        if (missing.isNotEmpty()) {
            throw GradleException(
                buildString {
                    appendLine("Bundled web screenshot runtime is incomplete for target '$target'.")
                    appendLine("Missing resources:")
                    missing.forEach { appendLine("- $it") }
                    appendLine("Suggested setup:")
                    appendLine("1. Prepare the offline runtime payload")
                    appendLine("   ./gradlew prepareBundledWebScreenshotRuntime")
                    appendLine("2. Re-run packaging")
                }
            )
        }
    }
}

tasks.matching {
    it.name in setOf(
        "createDistributable",
        "packageDistributionForCurrentOS",
        "packageDmg",
        "packageMsi",
        "packageExe",
        "packageRpm"
    )
}.configureEach {
    dependsOn(verifyBundledWebScreenshotRuntime)
}

val downloadBundledNode by tasks.registering {
    group = "distribution"
    description = "Download and unpack bundled Node.js into the staged offline runtime payload."

    val version = bundledNodeVersion.get()
    val distPlatform = getBundledNodeDistPlatform()
    val archiveExtension = getBundledNodeArchiveExtension()
    val archiveFile = layout.buildDirectory.file("tmp/bundled-node/node-v$version-$distPlatform.$archiveExtension")
    val extractDir = layout.buildDirectory.dir("tmp/bundled-node/extracted/$target")
    val targetNodeDir = stagedWebRuntimeDir.map { it.dir("node").asFile }

    doLast {
        val versionValue = bundledNodeVersion.get()
        val runtimeRoot = stagedWebRuntimeDir.get().asFile
        val nodeRoot = targetNodeDir.get()
        val versionMarker = File(nodeRoot, ".bundled-node-version")
        val existingNode = findBundledNodeExecutableInRuntime(runtimeRoot)
        if (existingNode != null && versionMarker.exists() && versionMarker.readText().trim() == versionValue) {
            logger.lifecycle("Bundled Node.js already prepared at ${nodeRoot.absolutePath}, skipping download.")
            return@doLast
        }

        val downloadUrl = "https://nodejs.org/dist/v$versionValue/${getBundledNodeExtractedDirName(versionValue)}.$archiveExtension"
        val archive = archiveFile.get().asFile
        val extractedRoot = extractDir.get().asFile

        archive.parentFile.mkdirs()
        extractedRoot.mkdirs()

        logger.lifecycle("Downloading bundled Node.js from $downloadUrl")
        URL(downloadUrl).openStream().use { input ->
            FileOutputStream(archive).use { output ->
                input.copyTo(output)
            }
        }

        project.delete(extractedRoot)
        extractedRoot.mkdirs()

        copy {
            from(
                if (archiveExtension == "zip") {
                    zipTree(archive)
                } else {
                    tarTree(resources.gzip(archive))
                }
            )
            into(extractedRoot)
        }

        val extractedNodeDir = File(extractedRoot, getBundledNodeExtractedDirName(versionValue))
        if (!extractedNodeDir.exists()) {
            throw GradleException("Downloaded Node.js archive did not contain ${extractedNodeDir.absolutePath}")
        }

        project.delete(nodeRoot)
        nodeRoot.parentFile.mkdirs()

        copy {
            from(extractedNodeDir)
            into(nodeRoot)
        }

        if (targetOs != "windows") {
            listOf(
                File(nodeRoot, "bin/node"),
                File(nodeRoot, "node")
            ).filter { it.exists() }.forEach { file ->
                file.setExecutable(true)
            }
        }

        versionMarker.writeText("$versionValue\n")

        logger.lifecycle("Bundled Node.js prepared at ${nodeRoot.absolutePath}")
    }
}

val installBundledPlaywrightRuntime by tasks.registering {
    group = "distribution"
    description = "Stage the offline Playwright runtime that will be extracted on first use."

    dependsOn(downloadBundledNode)
    val sourceRuntimeRoot = findWebScreenshotRuntimeRoot(resourcesRootDir) ?: resourcesRootDir
    val runtimeRoot = stagedWebRuntimeDir.get().asFile
    val lockFile = File(sourceRuntimeRoot, "package-lock.json")
    val packageFile = File(sourceRuntimeRoot, "package.json")
    val runtimeStampFile = File(runtimeRoot, ".playwright-runtime-stamp")

    inputs.file(packageFile)

    doLast {
        runtimeRoot.mkdirs()

        copy {
            from(File(sourceRuntimeRoot, "web-screenshot.js"))
            from(packageFile)
            if (lockFile.exists()) {
                from(lockFile)
            }
            into(runtimeRoot)
        }

        val nodeDir = findBundledNodeExecutableInRuntime(runtimeRoot)?.parentFile?.let { parent ->
            if (targetOs == "windows") parent else parent.parentFile
        } ?: throw GradleException("Bundled Node.js is missing. Run downloadBundledNode first.")

        val lockHash = if (lockFile.exists()) sha256(lockFile) else sha256(packageFile)
        val expectedStamp = buildString {
            append("node=")
            append(bundledNodeVersion.get())
            append('\n')
            append("lock=")
            append(lockHash)
            append('\n')
            append("target=")
            append(target)
            append('\n')
        }

        if (runtimeStampFile.exists() &&
            runtimeStampFile.readText() == expectedStamp &&
            File(runtimeRoot, "node_modules/playwright/package.json").exists() &&
            File(runtimeRoot, "node_modules/playwright-core/package.json").exists() &&
            hasInstalledPlaywrightBrowsersInRuntime(runtimeRoot)
        ) {
            logger.lifecycle("Bundled Playwright runtime already installed in ${runtimeRoot.absolutePath}, skipping npm install.")
            return@doLast
        }

        val nodeExecutable = getBundledNodeCommand(nodeDir)
        val npmCli = getNpmCliScript(nodeDir)
        val npxCli = getNpxCliScript(nodeDir)

        logger.lifecycle("Installing web screenshot npm dependencies in ${runtimeRoot.absolutePath}")
        exec {
            workingDir = runtimeRoot
            executable = nodeExecutable.absolutePath
            args = listOf(npmCli.absolutePath, "ci")
            environment("PLAYWRIGHT_BROWSERS_PATH", "0")
        }

        logger.lifecycle("Installing bundled Chromium in ${runtimeRoot.absolutePath}")
        val playwrightInstallArgs = if (npxCli.name == "npm-cli.js") {
            listOf(npxCli.absolutePath, "exec", "playwright", "install", "chromium")
        } else {
            listOf(npxCli.absolutePath, "playwright", "install", "chromium")
        }
        exec {
            workingDir = runtimeRoot
            executable = nodeExecutable.absolutePath
            args = playwrightInstallArgs
            environment("PLAYWRIGHT_BROWSERS_PATH", "0")
        }

        runtimeStampFile.writeText(expectedStamp)
    }
}

val packageBundledWebScreenshotRuntime by tasks.registering(Zip::class) {
    group = "distribution"
    description = "Create the offline runtime zip that the app will extract on first screenshot use."

    dependsOn(installBundledPlaywrightRuntime)
    from(stagedWebRuntimeDir)
    destinationDirectory.set(packagedWebRuntimeDir)
    archiveFileName.set("runtime.zip")
}

val cleanupLegacyWebScreenshotPackagingResources by tasks.registering {
    group = "distribution"
    description = "Remove legacy bundled web screenshot executables from source resources so packaging stays close to main."

    doNotTrackState("This task cleans legacy packaging artifacts from source resources in place.")

    doLast {
        listOf(
            File(resourcesRootDir, "node_modules"),
            File(resourcesRootDir, "common/node_modules"),
            File(resourcesRootDir, "ms-playwright"),
            File(resourcesRootDir, "common/ms-playwright"),
            File(resourcesRootDir, ".playwright-runtime-stamp"),
            File(resourcesRootDir, "macos-arm64/node"),
            File(resourcesRootDir, "macos-x64/node"),
            File(resourcesRootDir, "linux-arm64/node"),
            File(resourcesRootDir, "linux-x64/node"),
            File(resourcesRootDir, "windows/node")
        ).forEach { path ->
            if (path.exists()) {
                project.delete(path)
            }
        }
    }
}

val preparePackagedResources by tasks.registering {
    group = "distribution"
    description = "Write the offline web screenshot payload into standard resources layout."

    dependsOn(packageBundledWebScreenshotRuntime, cleanupLegacyWebScreenshotPackagingResources)
    doNotTrackState("This task prepares standard source resources in place for Compose Desktop packaging.")

    doLast {
        packagedWebRuntimeResourceDir.mkdirs()
        copy {
            from(packagedWebRuntimeZip)
            into(packagedWebRuntimeResourceDir)
        }
    }
}

val prepareBundledWebScreenshotRuntime by tasks.registering {
    group = "distribution"
    description = "Prepare the offline web screenshot payload for packaging."

    dependsOn(preparePackagedResources)
}

val cleanNativeDistributionOutputs by tasks.registering {
    group = "distribution"
    description = "Remove stale native distribution outputs so old app resources are not reused across packaging runs."

    doNotTrackState("This task deletes generated packaging outputs before creating new native bundles.")

    doLast {
        listOf(
            File(mOutputDir, "main/app"),
            File(mOutputDir, "main/dmg"),
            File(mOutputDir, "main/msi"),
            File(mOutputDir, "main/exe"),
            File(mOutputDir, "main/rpm")
        ).forEach { dir ->
            if (dir.exists()) {
                project.delete(dir)
            }
        }
    }
}

verifyBundledWebScreenshotRuntime.configure {
    dependsOn(prepareBundledWebScreenshotRuntime)
}

val packageCurrentOsWithBundledWebRuntime by tasks.registering {
    group = "distribution"
    description = "Prepare bundled web screenshot runtime and package for the current OS."

    val packageTaskName = when (targetOs) {
        "macos" -> "packageDmg"
        "windows" -> "packageExe"
        "linux" -> "packageRpm"
        else -> error("Unsupported target OS: $targetOs")
    }

    dependsOn(packageTaskName)
}

val currentOsPackageTaskName = when (targetOs) {
    "macos" -> "packageDmg"
    "windows" -> "packageExe"
    "linux" -> "packageRpm"
    else -> error("Unsupported target OS: $targetOs")
}

tasks.matching { it.name == currentOsPackageTaskName }.configureEach {
    dependsOn(cleanNativeDistributionOutputs, prepareBundledWebScreenshotRuntime)
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

compose.desktop {
    application {
        mainClass = "MainKt"
        buildTypes.release.proguard {
            configurationFiles.from(project.file("compose-desktop.pro"))
        }
        nativeDistributions {
            outputBaseDir.set(mOutputDir)   //build/output
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Exe, TargetFormat.Rpm)
            appResourcesRootDir.set(project.layout.projectDirectory.dir("resources"))
            packageName = "Monica-$targetArch"
            packageVersion = "${rootProject.extra["app.version"]}"
            description = "Monica is a cross-platform image editor"
            copyright = "© 2024 Tony Shen. All rights reserved."

            jvmArgs += listOf("-Xms4G","-Xmx4G")
            jvmArgs += listOf("-Dlogback.debug=true")

            includeAllModules = true    //包含所有模块

            macOS {
                bundleID = "cn.netdiscovery.monica"
                dockName = "monica"
            }

            windows {
                console = false    // 为应用程序添加一个控制台启动器
                shortcut = true    // 桌面快捷方式
                dirChooser = true  // 允许在安装过程中自定义安装路径
                perUserInstall = false   //允许在每个用户的基础上安装应用程序
                menuGroup = "start-menu-group"
                upgradeUuid = "b329caf3-6681-49b9-98d0-adb34d32e130"
                iconFile.set(project.file("src/jvmMain/resources/images/launcher.ico"))
            }
        }
    }
}


================================================
FILE: compose-desktop.pro
================================================
-dontwarn org.slf4j.**
-dontwarn ch.qos.logback.**
-dontwarn com.google.gson.**
-dontwarn org.apache.**
-dontwarn com.safframework.rxcache.**

-keep class cn.netdiscovery.monica.** {*;}
-keep interface ccn.netdiscovery.monica.** {*;}
-keep enum cn.netdiscovery.monica.** {*;}

================================================
FILE: config/build.gradle.kts
================================================
 plugins {
    kotlin("jvm")
    id("com.github.gmazzo.buildconfig") version "5.4.0"
}

repositories {
    mavenCentral()
    maven { url = uri("https://jitpack.io") }
}

val requestedTaskNames = gradle.startParameter.taskNames

val isPackagingBuild = requestedTaskNames.any { taskName ->
    val normalized = taskName.substringAfterLast(':')
    normalized in setOf(
        "packageCurrentOsWithBundledWebRuntime",
        "packageDmg",
        "packageMsi",
        "packageExe",
        "packageRpm",
        "packageDistributionForCurrentOS",
        "createDistributable"
    )
}

val isProVersion = providers.gradleProperty("isProVersion")
    .map(String::toBoolean)
    .orElse(isPackagingBuild)
    .get()

buildConfig {
    useKotlinOutput { topLevelConstants = true }
    useKotlinOutput { internalVisibility = false }   // adds `internal` modifier to all declarations

    buildConfigField("APP_NAME", project.name)
    buildConfigField("APP_VERSION", "${rootProject.extra["app.version"]}")
    buildConfigField("KOTLIN_VERSION", "${rootProject.extra["kotlin.version"]}")
    buildConfigField("COMPOSE_VERSION", "${rootProject.extra["compose.version"]}")
    buildConfigField("IS_PRO_VERSION", isProVersion)
    buildConfigField("BUILD_TIME", System.currentTimeMillis())
}

dependencies {
    testImplementation(kotlin("test"))
    implementation ("org.jetbrains.kotlin:kotlin-stdlib")
    
    // Logging
    implementation("ch.qos.logback:logback-classic:${rootProject.extra["logback"]}")
    implementation("ch.qos.logback:logback-core:${rootProject.extra["logback"]}")
    
    // Gson for JSON serialization
    implementation("com.google.code.gson:gson:2.10.1")
    
    // Domain module (for GeneralSettings)
    implementation(project(":domain"))
    
    // RxCache (for type definitions, instance will be provided by main project)
    implementation("com.github.fengzhizi715.RxCache:core:${rootProject.extra["rxcache"]}")
    implementation("com.github.fengzhizi715.RxCache:extension:${rootProject.extra["rxcache"]}")
}

tasks.test {
    useJUnitPlatform()
}
kotlin {
    jvmToolchain(17)
}


================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/Constants.kt
================================================
package cn.netdiscovery.monica.config

import java.text.SimpleDateFormat


/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.config.Constants
 * @author: Tony Shen
 * @date: 2024/5/7 10:55
 * @version: V1.0 <描述当前版本功能>
 */
val appVersion by lazy {
    Monica.config.BuildConfig.APP_VERSION
}

val kotlinVersion by lazy {
    Monica.config.BuildConfig.KOTLIN_VERSION
}

val composeVersion by lazy {
    Monica.config.BuildConfig.COMPOSE_VERSION
}

val isProVersion by lazy {
    Monica.config.BuildConfig.IS_PRO_VERSION
}

val buildTime:String by lazy {
   val time = Monica.config.BuildConfig.BUILD_TIME

    val dateformat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    dateformat.format(time)
}


const val KEY_CROP_FIRST = "key_crop_first"
const val KEY_CROP_SECOND = "key_crop_second"
const val KEY_CROP = "key_crop"
const val KEY_GENERAL_SETTINGS = "key_general_settings"

const val STATUS_HTTP_SERVER_OK = 1
const val STATUS_HTTP_SERVER_FAILED = 0

const val MODULE_COLOR = "module_color"
const val MODULE_OPENCV = "module_opencv"

================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/SystemConstants.kt
================================================
package cn.netdiscovery.monica.config


/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.utils.SystemUtils
 * @author: Tony Shen
 * @date:  2024/7/6 14:36
 * @version: V1.0 <描述当前版本功能>
 */

val os: String = System.getProperty("os.name")
val arch: String = System.getProperty("os.arch")
val osVersion: String = System.getProperty("os.version")
val javaVersion: String = System.getProperty("java.version")
val javaVendor: String = System.getProperty("java.vendor")
val workDirectory: String = System.getProperty("user.dir")
val userHome: String = System.getProperty("user.home")

val isMac by lazy {
    os.contains("Mac")
}

val isWindows by lazy {
    os.startsWith("Win")
}

val isLinux by lazy {
    os.contains("nux") || os.contains("nix")
}

================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigCategory.kt
================================================
package cn.netdiscovery.monica.config.category

/**
 * 配置分类枚举
 * 
 * 用于区分不同类型的配置,便于统一管理和验证。
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
enum class ConfigCategory {
    /**
     * 应用设置(用户可修改的设置,如 GeneralSettings)
     */
    APP_SETTINGS,
    
    /**
     * UI 配置(UI 相关的配置,如滤镜参数元数据)
     */
    UI_CONFIG,
    
    /**
     * 业务配置(业务逻辑相关的配置,如 API 密钥、算法 URL)
     */
    BUSINESS_CONFIG,
    
    /**
     * 用户偏好(用户个人偏好设置,如语言、主题)
     */
    USER_PREFERENCE,
    
    /**
     * 临时配置(临时存储的配置,如裁剪状态)
     */
    TEMPORARY
}



================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigCategoryManager.kt
================================================
package cn.netdiscovery.monica.config.category

import cn.netdiscovery.monica.config.storage.ConfigManager
import cn.netdiscovery.monica.config.storage.ConfigStorage
import cn.netdiscovery.monica.config.storage.ConfigType
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
 * 配置分类管理器
 * 
 * 根据配置分类选择合适的存储和验证策略。
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
object ConfigCategoryManager {
    
    private val logger: Logger = LoggerFactory.getLogger(ConfigCategoryManager::class.java)
    
    /**
     * 配置元信息
     */
    data class ConfigMetadata<T>(
        val key: String,
        val category: ConfigCategory,
        val storageType: ConfigType,
        val validator: ConfigValidator<T>? = null,
        val defaultValue: T
    )
    
    /**
     * 配置注册表
     */
    private val configRegistry = mutableMapOf<String, ConfigMetadata<*>>()
    
    /**
     * 注册配置
     */
    fun <T> register(metadata: ConfigMetadata<T>) {
        configRegistry[metadata.key] = metadata
        logger.debug("Registered config: key=${metadata.key}, category=${metadata.category}")
    }
    
    /**
     * 获取配置元信息
     */
    @Suppress("UNCHECKED_CAST")
    fun <T> getMetadata(key: String): ConfigMetadata<T>? {
        return configRegistry[key] as? ConfigMetadata<T>
    }
    
    /**
     * 保存配置(带验证)
     */
    fun <T> save(key: String, value: T): ValidationResult {
        val metadata = getMetadata<T>(key)
        
        // 验证配置值
        metadata?.validator?.let { validator ->
            val error = validator.validate(value)
            if (error != null) {
                logger.warn("Config validation failed: key=$key, error=$error")
                return ValidationResult.failure(error)
            }
        }
        
        // 保存配置
        try {
            val storageType = metadata?.storageType ?: ConfigType.DEFAULT
            ConfigManager.save(key, value, storageType)
            logger.debug("Config saved: key=$key, category=${metadata?.category}")
            return ValidationResult.success()
        } catch (e: Exception) {
            logger.error("Failed to save config: key=$key", e)
            return ValidationResult.failure("Failed to save config: ${e.message}")
        }
    }
    
    /**
     * 加载配置(带默认值)
     */
    @Suppress("UNCHECKED_CAST")
    fun <T> load(key: String): T? {
        val metadata = getMetadata<T>(key)
        val storageType = metadata?.storageType ?: ConfigType.DEFAULT
        val defaultValue = metadata?.defaultValue
        
        return if (defaultValue != null) {
            ConfigManager.load(key, defaultValue, storageType) as T
        } else {
            logger.warn("No default value for config: key=$key")
            null
        }
    }
    
    /**
     * 加载配置(带自定义默认值)
     */
    fun <T> load(key: String, default: T): T {
        val metadata = getMetadata<T>(key)
        val storageType = metadata?.storageType ?: ConfigType.DEFAULT
        return ConfigManager.load(key, default, storageType)
    }
    
    /**
     * 验证配置值(不保存)
     */
    fun <T> validate(key: String, value: T): ValidationResult {
        val metadata = getMetadata<T>(key)
        val validator = metadata?.validator ?: return ValidationResult.success()
        
        val error = validator.validate(value)
        return if (error != null) {
            ValidationResult.failure(error)
        } else {
            ValidationResult.success()
        }
    }
    
    /**
     * 获取配置的存储类型
     */
    fun getStorageType(key: String): ConfigType {
        return getMetadata<Any>(key)?.storageType ?: ConfigType.DEFAULT
    }
    
    /**
     * 获取配置的分类
     */
    fun getCategory(key: String): ConfigCategory? {
        return getMetadata<Any>(key)?.category
    }
    
    /**
     * 获取所有已注册的配置键
     */
    fun getAllKeys(): List<String> {
        return configRegistry.keys.toList()
    }
    
    /**
     * 获取指定分类的所有配置键
     */
    fun getKeysByCategory(category: ConfigCategory): List<String> {
        return configRegistry.filter { it.value.category == category }.keys.toList()
    }
    
    /**
     * 清除指定分类的所有配置
     */
    fun clearCategory(category: ConfigCategory) {
        val keys = getKeysByCategory(category)
        keys.forEach { key ->
            val metadata = getMetadata<Any>(key)
            val storageType = metadata?.storageType ?: ConfigType.DEFAULT
            ConfigManager.remove(key, storageType)
        }
        logger.info("Cleared all configs in category: $category")
    }
}



================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigDefinitions.kt
================================================
package cn.netdiscovery.monica.config.category

import cn.netdiscovery.monica.config.KEY_GENERAL_SETTINGS
import cn.netdiscovery.monica.config.storage.ConfigType
import cn.netdiscovery.monica.domain.GeneralSettings

/**
 * 配置定义
 * 
 * 集中定义所有配置的元信息,包括分类、存储类型、验证规则和默认值。
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
object ConfigDefinitions {
    
    /**
     * 初始化所有配置定义
     */
    fun initialize() {
        registerAppSettings()
        registerUserPreferences()
        registerTemporaryConfigs()
    }
    
    /**
     * 注册应用设置
     */
    private fun registerAppSettings() {
        // GeneralSettings
        ConfigCategoryManager.register(
            ConfigCategoryManager.ConfigMetadata(
                key = KEY_GENERAL_SETTINGS,
                category = ConfigCategory.APP_SETTINGS,
                storageType = ConfigType.RX_CACHE,
                defaultValue = GeneralSettings(
                    outputBoxR = 255,
                    outputBoxG = 255,
                    outputBoxB = 255,
                    size = 512,
                    maxHistorySize = 50,
                    deepSeekApiKey = "",
                    geminiApiKey = "",
                    algorithmUrl = "",
                    themeId = "LIGHT"
                )
            )
        )
    }
    
    /**
     * 注册用户偏好设置
     */
    private fun registerUserPreferences() {
        // 语言设置
        ConfigCategoryManager.register(
            ConfigCategoryManager.ConfigMetadata(
                key = "selected_language",
                category = ConfigCategory.USER_PREFERENCE,
                storageType = ConfigType.PREFERENCES,
                defaultValue = "zh"
            )
        )
    }
    
    /**
     * 注册临时配置
     */
    private fun registerTemporaryConfigs() {
        // 裁剪相关临时配置已在 Constants.kt 中定义
        // 这里可以添加其他临时配置的定义
    }
}

================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigValidator.kt
================================================
package cn.netdiscovery.monica.config.category

import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
 * 配置验证器接口
 * 
 * 用于验证配置值的有效性。
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
interface ConfigValidator<T> {
    /**
     * 验证配置值
     * 
     * @param value 配置值
     * @return 验证结果,如果有效返回 null,否则返回错误信息
     */
    fun validate(value: T): String?
}

/**
 * 配置验证结果
 */
data class ValidationResult(
    val isValid: Boolean,
    val errorMessage: String? = null
) {
    companion object {
        fun success() = ValidationResult(true)
        fun failure(message: String) = ValidationResult(false, message)
    }
}

/**
 * 通用配置验证器
 */
object CommonValidators {
    
    private val logger: Logger = LoggerFactory.getLogger(CommonValidators::class.java)
    
    /**
     * 字符串非空验证器
     */
    fun nonEmptyString(): ConfigValidator<String> = object : ConfigValidator<String> {
        override fun validate(value: String): String? {
            return if (value.isBlank()) {
                "Value cannot be empty"
            } else {
                null
            }
        }
    }
    
    /**
     * 字符串长度验证器
     */
    fun stringLength(min: Int, max: Int): ConfigValidator<String> = object : ConfigValidator<String> {
        override fun validate(value: String): String? {
            return when {
                value.length < min -> "Value length must be at least $min"
                value.length > max -> "Value length must be at most $max"
                else -> null
            }
        }
    }
    
    /**
     * 数值范围验证器
     */
    fun <T : Number> numberRange(min: T, max: T): ConfigValidator<T> = object : ConfigValidator<T> {
        override fun validate(value: T): String? {
            val doubleValue = value.toDouble()
            val minValue = min.toDouble()
            val maxValue = max.toDouble()
            return when {
                doubleValue < minValue -> "Value must be at least $min"
                doubleValue > maxValue -> "Value must be at most $max"
                else -> null
            }
        }
    }
    
    /**
     * 整数范围验证器
     */
    fun intRange(min: Int, max: Int): ConfigValidator<Int> = object : ConfigValidator<Int> {
        override fun validate(value: Int): String? {
            return when {
                value < min -> "Value must be at least $min"
                value > max -> "Value must be at most $max"
                else -> null
            }
        }
    }
    
    /**
     * URL 验证器
     */
    fun url(): ConfigValidator<String> = object : ConfigValidator<String> {
        override fun validate(value: String): String? {
            return try {
                java.net.URL(value)
                null
            } catch (e: Exception) {
                "Invalid URL format: $value"
            }
        }
    }
    
    /**
     * 组合验证器(多个验证器同时生效)
     */
    fun <T> combine(vararg validators: ConfigValidator<T>): ConfigValidator<T> = object : ConfigValidator<T> {
        override fun validate(value: T): String? {
            validators.forEach { validator ->
                validator.validate(value)?.let { return it }
            }
            return null
        }
    }
    
    /**
     * 可选验证器(值为 null 时跳过验证)
     */
    fun <T> optional(validator: ConfigValidator<T>): ConfigValidator<T?> = object : ConfigValidator<T?> {
        override fun validate(value: T?): String? {
            return if (value == null) {
                null
            } else {
                validator.validate(value)
            }
        }
    }
}



================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/storage/ConfigManager.kt
================================================
package cn.netdiscovery.monica.config.storage

import com.safframework.rxcache.RxCache
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
 * 统一配置管理器
 * 
 * 管理不同类型的配置存储,提供统一的配置访问接口。
 * 支持多种存储后端:
 * - RxCache: 用于复杂对象(如 GeneralSettings)
 * - Preferences: 用于简单键值对(如语言设置)
 * - File: 用于 JSON 配置文件(如滤镜参数元数据)
 * 
 * 注意:需要先调用 initialize() 方法初始化,传入 RxCache 实例。
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
object ConfigManager {
    
    private val logger: Logger = LoggerFactory.getLogger(ConfigManager::class.java)
    
    private var _rxCacheStorage: ConfigStorage? = null
    
    /**
     * RxCache 存储(用于复杂对象)
     */
    val rxCacheStorage: ConfigStorage
        get() = _rxCacheStorage ?: throw IllegalStateException("ConfigManager not initialized. Call initialize() first.")
    
    /**
     * Preferences 存储(用于简单键值对)
     */
    val preferencesStorage: ConfigStorage = PreferencesConfigStorage()
    
    /**
     * 默认存储(优先使用 RxCache)
     */
    val defaultStorage: ConfigStorage
        get() = rxCacheStorage
    
    /**
     * 初始化 ConfigManager
     * 
     * @param rxCache RxCache 实例
     */
    fun initialize(rxCache: RxCache) {
        _rxCacheStorage = RxCacheConfigStorage(rxCache)
        logger.info("ConfigManager initialized")
    }
    
    /**
     * 根据配置类型选择合适的存储
     * 
     * @param configType 配置类型
     * @return 对应的存储实例
     */
    fun getStorage(configType: ConfigType = ConfigType.DEFAULT): ConfigStorage {
        return when (configType) {
            ConfigType.RX_CACHE -> rxCacheStorage
            ConfigType.PREFERENCES -> preferencesStorage
            ConfigType.DEFAULT -> defaultStorage
        }
    }
    
    /**
     * 保存配置(使用默认存储)
     */
    fun <T> save(key: String, value: T, configType: ConfigType = ConfigType.DEFAULT) {
        try {
            getStorage(configType).save(key, value)
            logger.debug("Config saved: key=$key, type=$configType")
        } catch (e: Exception) {
            logger.error("Failed to save config: key=$key, type=$configType", e)
            throw e
        }
    }
    
    /**
     * 加载配置(使用默认存储)
     */
    fun <T> load(key: String, default: T, configType: ConfigType = ConfigType.DEFAULT): T {
        return try {
            val value = getStorage(configType).load(key, default)
            logger.debug("Config loaded: key=$key, type=$configType, found=${value != default}")
            value
        } catch (e: Exception) {
            logger.warn("Failed to load config: key=$key, type=$configType, using default", e)
            default
        }
    }
    
    /**
     * 检查配置是否存在
     */
    fun exists(key: String, configType: ConfigType = ConfigType.DEFAULT): Boolean {
        return getStorage(configType).exists(key)
    }
    
    /**
     * 删除配置
     */
    fun remove(key: String, configType: ConfigType = ConfigType.DEFAULT) {
        try {
            getStorage(configType).remove(key)
            logger.debug("Config removed: key=$key, type=$configType")
        } catch (e: Exception) {
            logger.error("Failed to remove config: key=$key, type=$configType", e)
            throw e
        }
    }
    
    /**
     * 清空指定类型的配置
     */
    fun clear(configType: ConfigType = ConfigType.DEFAULT) {
        try {
            getStorage(configType).clear()
            logger.info("Config cleared: type=$configType")
        } catch (e: Exception) {
            logger.error("Failed to clear config: type=$configType", e)
            throw e
        }
    }
    
    /**
     * 获取所有配置键
     */
    fun getAllKeys(configType: ConfigType = ConfigType.DEFAULT): List<String> {
        return getStorage(configType).getAllKeys()
    }
}

/**
 * 配置类型枚举
 */
enum class ConfigType {
    /**
     * 使用 RxCache 存储(默认,用于复杂对象)
     */
    RX_CACHE,
    
    /**
     * 使用 Preferences 存储(用于简单键值对)
     */
    PREFERENCES,
    
    /**
     * 使用默认存储(当前为 RxCache)
     */
    DEFAULT
}



================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/storage/ConfigStorage.kt
================================================
package cn.netdiscovery.monica.config.storage

/**
 * 统一配置存储接口
 * 
 * 抽象了不同存储实现的差异,提供统一的配置读写接口。
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
interface ConfigStorage {
    /**
     * 保存配置值
     * 
     * @param key 配置键
     * @param value 配置值(支持基本类型和可序列化对象)
     */
    fun <T> save(key: String, value: T)
    
    /**
     * 加载配置值
     * 
     * @param key 配置键
     * @param default 默认值(当配置不存在时返回)
     * @return 配置值,如果不存在则返回默认值
     */
    fun <T> load(key: String, default: T): T
    
    /**
     * 检查配置是否存在
     * 
     * @param key 配置键
     * @return 如果配置存在返回 true,否则返回 false
     */
    fun exists(key: String): Boolean
    
    /**
     * 删除配置
     * 
     * @param key 配置键
     */
    fun remove(key: String)
    
    /**
     * 清空所有配置
     */
    fun clear()
    
    /**
     * 获取所有配置键
     * 
     * @return 配置键列表
     */
    fun getAllKeys(): List<String>
}



================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/storage/FileConfigStorage.kt
================================================
package cn.netdiscovery.monica.config.storage

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardOpenOption

/**
 * 文件配置存储适配器(JSON 格式)
 * 
 * 用于存储 JSON 格式的配置文件(如滤镜参数元数据)。
 * 所有配置存储在一个 JSON 文件中。
 * 
 * @param configFile 配置文件路径
 * @param gson Gson 实例,用于序列化/反序列化
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
class FileConfigStorage(
    private val configFile: File,
    private val gson: Gson = Gson()
) : ConfigStorage {
    
    private val logger: Logger = LoggerFactory.getLogger(FileConfigStorage::class.java)
    
    private val configMap: MutableMap<String, Any> by lazy {
        loadFromFile()
    }
    
    /**
     * 从文件加载配置
     */
    private fun loadFromFile(): MutableMap<String, Any> {
        return if (configFile.exists() && configFile.isFile) {
            try {
                val jsonContent = configFile.readText(Charsets.UTF_8)
                if (jsonContent.isBlank()) {
                    mutableMapOf()
                } else {
                    val type = object : TypeToken<Map<String, Any>>() {}.type
                    gson.fromJson(jsonContent, type) ?: mutableMapOf()
                }
            } catch (e: Exception) {
                logger.error("Failed to load config from file: ${configFile.absolutePath}", e)
                mutableMapOf()
            }
        } else {
            // 文件不存在,创建空配置
            mutableMapOf()
        }
    }
    
    /**
     * 保存配置到文件
     */
    private fun saveToFile() {
        try {
            // 确保父目录存在
            configFile.parentFile?.mkdirs()
            
            val jsonContent = gson.toJson(configMap)
            Files.write(
                configFile.toPath(),
                jsonContent.toByteArray(Charsets.UTF_8),
                StandardOpenOption.CREATE,
                StandardOpenOption.TRUNCATE_EXISTING,
                StandardOpenOption.WRITE
            )
        } catch (e: Exception) {
            logger.error("Failed to save config to file: ${configFile.absolutePath}", e)
            throw ConfigStorageException("Failed to save config to file", e)
        }
    }
    
    override fun <T> save(key: String, value: T) {
        try {
            configMap[key] = value as Any
            saveToFile()
        } catch (e: Exception) {
            logger.error("Failed to save config with key: $key", e)
            throw ConfigStorageException("Failed to save config: $key", e)
        }
    }
    
    @Suppress("UNCHECKED_CAST")
    override fun <T> load(key: String, default: T): T {
        return try {
            val value = configMap[key]
            if (value != null) {
                // 尝试类型转换
                when {
                    default is String && value is String -> value as T
                    default is Int && value is Number -> value.toInt() as T
                    default is Long && value is Number -> value.toLong() as T
                    default is Float && value is Number -> value.toFloat() as T
                    default is Double && value is Number -> value.toDouble() as T
                    default is Boolean && value is Boolean -> value as T
                    else -> {
                        // 尝试使用 Gson 进行类型转换
                        val jsonValue = gson.toJson(value)
                        gson.fromJson(jsonValue, default!!::class.java) as T
                    }
                }
            } else {
                default
            }
        } catch (e: Exception) {
            logger.warn("Failed to load config with key: $key, using default value", e)
            default
        }
    }
    
    override fun exists(key: String): Boolean {
        return configMap.containsKey(key)
    }
    
    override fun remove(key: String) {
        try {
            configMap.remove(key)
            saveToFile()
        } catch (e: Exception) {
            logger.error("Failed to remove config with key: $key", e)
            throw ConfigStorageException("Failed to remove config: $key", e)
        }
    }
    
    override fun clear() {
        try {
            configMap.clear()
            saveToFile()
        } catch (e: Exception) {
            logger.error("Failed to clear config storage", e)
            throw ConfigStorageException("Failed to clear config storage", e)
        }
    }
    
    override fun getAllKeys(): List<String> {
        return configMap.keys.toList()
    }
}

================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/storage/PreferencesConfigStorage.kt
================================================
package cn.netdiscovery.monica.config.storage

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.prefs.Preferences

/**
 * Preferences 配置存储适配器
 * 
 * 适配 Java Preferences API,用于存储简单的键值对配置(如语言设置)。
 * 
 * @param preferencesNode Preferences 节点,默认为用户节点
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
class PreferencesConfigStorage(
    private val preferencesNode: Preferences = Preferences.userNodeForPackage(PreferencesConfigStorage::class.java)
) : ConfigStorage {
    
    private val logger: Logger = LoggerFactory.getLogger(PreferencesConfigStorage::class.java)
    
    override fun <T> save(key: String, value: T) {
        try {
            when (value) {
                is String -> preferencesNode.put(key, value)
                is Int -> preferencesNode.putInt(key, value)
                is Long -> preferencesNode.putLong(key, value)
                is Float -> preferencesNode.putFloat(key, value)
                is Double -> preferencesNode.putDouble(key, value)
                is Boolean -> preferencesNode.putBoolean(key, value)
                is ByteArray -> preferencesNode.putByteArray(key, value)
                else -> {
                    // 对于复杂对象,序列化为 JSON 字符串
                    preferencesNode.put(key, value.toString())
                    logger.warn("Complex object serialized as string for key: $key")
                }
            }
            preferencesNode.flush()
        } catch (e: Exception) {
            logger.error("Failed to save config with key: $key", e)
            throw ConfigStorageException("Failed to save config: $key", e)
        }
    }
    
    @Suppress("UNCHECKED_CAST")
    override fun <T> load(key: String, default: T): T {
        return try {
            when (default) {
                is String -> preferencesNode.get(key, default) as T
                is Int -> preferencesNode.getInt(key, default) as T
                is Long -> preferencesNode.getLong(key, default) as T
                is Float -> preferencesNode.getFloat(key, default) as T
                is Double -> preferencesNode.getDouble(key, default) as T
                is Boolean -> preferencesNode.getBoolean(key, default) as T
                is ByteArray -> preferencesNode.getByteArray(key, default) as T
                else -> {
                    val value = preferencesNode.get(key, null)
                    if (value != null) {
                        // 尝试从字符串反序列化(需要类型信息)
                        logger.warn("Complex object deserialization not fully supported for key: $key, returning default")
                        default
                    } else {
                        default
                    }
                }
            }
        } catch (e: Exception) {
            logger.warn("Failed to load config with key: $key, using default value", e)
            default
        }
    }
    
    override fun exists(key: String): Boolean {
        return try {
            preferencesNode.get(key, null) != null
        } catch (e: Exception) {
            logger.warn("Failed to check existence of config with key: $key", e)
            false
        }
    }
    
    override fun remove(key: String) {
        try {
            preferencesNode.remove(key)
            preferencesNode.flush()
        } catch (e: Exception) {
            logger.error("Failed to remove config with key: $key", e)
            throw ConfigStorageException("Failed to remove config: $key", e)
        }
    }
    
    override fun clear() {
        try {
            preferencesNode.clear()
            preferencesNode.flush()
        } catch (e: Exception) {
            logger.error("Failed to clear Preferences", e)
            throw ConfigStorageException("Failed to clear config storage", e)
        }
    }
    
    override fun getAllKeys(): List<String> {
        return try {
            preferencesNode.keys().toList()
        } catch (e: Exception) {
            logger.error("Failed to get all keys from Preferences", e)
            emptyList()
        }
    }
}



================================================
FILE: config/src/main/kotlin/cn/netdiscovery/monica/config/storage/RxCacheConfigStorage.kt
================================================
package cn.netdiscovery.monica.config.storage

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.safframework.rxcache.RxCache
import com.safframework.rxcache.ext.get
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
 * RxCache 配置存储适配器
 * 
 * 适配现有的 RxCache 实现,用于存储复杂对象(如 GeneralSettings)。
 * 
 * 注意:由于 RxCache 的 get 方法需要 reified 类型参数,我们使用 Any 类型进行通用处理。
 * 对于类型安全的场景,建议使用具体的类型调用。
 * 
 * @param rxCache RxCache 实例,由外部传入以避免循环依赖
 * 
 * @author: Tony Shen
 * @date: 2025-12-12
 */
class RxCacheConfigStorage(
    private val rxCache: RxCache
) : ConfigStorage {
    
    private val logger: Logger = LoggerFactory.getLogger(RxCacheConfigStorage::class.java)
    private val gson = Gson()
    
    override fun <T> save(key: String, value: T) {
        try {
            rxCache.saveOrUpdate(key, value)
        } catch (e: Exception) {
            logger.error("Failed to save config with key: $key", e)
            throw ConfigStorageException("Failed to save config: $key", e)
        }
    }
    
    @Suppress("UNCHECKED_CAST")
    override fun <T> load(key: String, default: T): T {
        return try {
            // RxCache.get 需要 reified 类型参数,这里使用 Any 作为通用类型
            // 实际使用时,类型转换由调用方保证(通过 default 参数的类型推断)
            val result = rxCache.get<Any>(key)?.data
            if (result != null) {
                // 尝试类型转换
                // 对于基本类型,进行显式转换
                when {
                    default is String && result is String -> result as T
                    default is Int && result is Number -> result.toInt() as T
                    default is Long && result is Number -> result.toLong() as T
                    default is Float && result is Number -> result.toFloat() as T
                    default is Double && result is Number -> result.toDouble() as T
                    default is Boolean && result is Boolean -> result as T
                    // 对于复杂对象,检查类型是否匹配
                    result::class.java.isAssignableFrom(default!!::class.java) -> result as T
                    default::class.java.isAssignableFrom(result::class.java) -> result as T
                    // 如果 result 是 LinkedTreeMap(Gson 反序列化的结果),尝试用 Gson 转换
                    result is com.google.gson.internal.LinkedTreeMap<*, *> -> {
                        try {
                            val json = gson.toJson(result)
                            @Suppress("UNCHECKED_CAST")
                            gson.fromJson(json, default!!::class.java) as T ?: default
                        } catch (e: Exception) {
                            logger.warn("Failed to convert LinkedTreeMap to ${default!!::class.java.simpleName} for key: $key", e)
                            default
                        }
                    }
                    else -> {
                        logger.warn("Type mismatch for key: $key, expected: ${default!!::class.java.simpleName}, got: ${result::class.java.simpleName}")
                        default
                    }
                }
            } else {
                default
            }
        } catch (e: Exception) {
            logger.warn("Failed to load config with key: $key, using default value", e)
            default
        }
    }
    
    override fun exists(key: String): Boolean {
        return try {
            rxCache.get<Any>(key) != null
        } catch (e: Exception) {
            logger.warn("Failed to check existence of config with key: $key", e)
            false
        }
    }
    
    override fun remove(key: String) {
        try {
            rxCache.remove(key)
        } catch (e: Exception) {
            logger.error("Failed to remove config with key: $key", e)
            throw ConfigStorageException("Failed to remove config: $key", e)
        }
    }
    
    override fun clear() {
        try {
            rxCache.clear()
        } catch (e: Exception) {
            logger.error("Failed to clear RxCache", e)
            throw ConfigStorageException("Failed to clear config storage", e)
        }
    }
    
    override fun getAllKeys(): List<String> {
        // RxCache 不直接提供获取所有键的接口,返回空列表
        // 如果需要此功能,可以考虑维护一个键列表
        logger.warn("RxCache does not support getAllKeys(), returning empty list")
        return emptyList()
    }
}

/**
 * 配置存储异常
 */
class ConfigStorageException(message: String, cause: Throwable? = null) : Exception(message, cause)



================================================
FILE: docs/filter_module_refactor.md
================================================
# 滤镜模块 UI 重构与优化说明(2025-12)

## 背景与目标

本次重构的核心目标:

- **提升 UI 可用性与一致性**:对齐、间距、状态提示更清晰,符合图像编辑软件的交互预期。
- **保持业务逻辑不变/可控演进**:在不破坏滤镜算法实现的前提下,整理 UI 状态与交互。
- **提升性能与稳定性**:拖动体验更顺滑,避免频繁计算与 CPU 抖动;修复已知崩溃点与错位问题。

---

## 关键交互语义(最终形态)

### 1)拖动即提交(去掉 Apply 按钮)

- **滤镜选择**:点击某个滤镜后,立即在编辑器画布上应用一次(并记录一次历史)。
- **参数调整**:
  - 拖动过程中:仍会以 **300ms 抽样**方式触发预览(降低计算频率)。
  - 松手后:**立即提交**到编辑器画布(并记录一次历史),避免多次历史碎片化。
  - 文本输入:输入过程只做预览;按 `Done` 后提交一次。

> 说明:提交时使用“进入滤镜模块前的基线图”作为输入,避免在 `currentImage` 上反复叠加导致效果漂移。

### 2)Reset / Cancel / 清除滤镜

- **Reset(重置滤镜)**:恢复当前滤镜的默认参数,并立即提交一次(记录历史)。
- **清除滤镜**:恢复到进入滤镜模块前的效果(基线图),记录一次历史,并取消滤镜选中态。
- **Cancel**:用于取消未提交的预览态(例如仅有 previewImage),回到上次提交参数快照并清理预览。

---

## UI 结构拆分与状态管理

### 1)去全局状态(多实例安全)

移除文件级全局变量 `filterSelectedIndex` / `filterTempMap`,改为在 `filter()` 内使用 `remember` 状态:

- `selectedIndexState`
- `paramMap`(`mutableStateMapOf`)
- `appliedParamSnapshot`
- `baseImageSnapshot`(进入模块前基线图)

避免了多窗口/多次进入模块时状态串扰的问题。

### 2)右侧面板底栏固定

修复了右侧面板滚动区域占满高度导致底部按钮区域不可见的问题:滚动区使用 `weight(1f)`,底栏固定展示。

### 3)收起时参数摘要

当参数区收起时,展示“参数摘要”卡片:

- 默认/已调整项数量
- 展示部分差异项
- Reset 提示(引导用户使用底部按钮)

---

## 参数范围与格式化(配置化)

新增参数 UI 元信息:

- `FilterParamMeta(min, max, step, decimals)`
- `FilterParamMetaRegistry.resolve(filterName, param)`:统一解析范围、步长、显示小数位。

并新增默认参数构建工具:

- `buildDefaultParamMap(filterName)`:用于初始化/Reset/判断是否处于默认参数状态。
- Float/Double 默认值按 `decimals` 统一格式化,避免 UI 显示不一致。

### BlockFilter 安全修复(step=0 崩溃)

问题:`BlockFilter` 内部将 `blockSize` 用于 `range.step(blockSize)`,当 `blockSize=0` 会直接抛异常。

修复策略(三道防线):

1. `FilterParamMetaRegistry` 为 `blockSize` 设置 `min=1`。
2. 默认参数构建时按 meta.min 对 Int 进行 clamp(即使缓存里有 0 也会被纠正)。
3. `BlockFilter` 构造函数内防御性修复:`max(1, blockSize)`,彻底杜绝 crash。

---

## 性能优化

### 1)Slider 抽样预览(300ms)+ 松手提交

拖动过程中不实时提交,降低重算频率;松手后一次性提交,历史更干净。

### 2)预览缓存(同滤镜 + 同参数 hash 命中)

在 `FilterViewModel.applyFilterPreview()` 中引入 LRU 预览缓存:

- Key:`baseImageId(identityHashCode) + filterName + paramsHash(稳定排序后 hash)`
- 策略:LRU + 双阈值淘汰(条目数 + 估算内存上限)
- `clear()` 时会清空缓存,避免跨页面持有内存

收益:重复参数回退/来回拖动时命中缓存,CPU 更稳、预览更顺滑。

---

## Bug 修复汇总

- **搜索列表点击/选中错位**:修复 `itemsIndexed` 使用位置 index 误当真实 filterIndex 的问题。
- **英文硬编码**:如 `No Image` 等占位文案改为 i18n。
- **右侧按钮不显示**:滚动区域 `fillMaxSize()` 挤掉底栏的问题修复为 `weight(1f)`。
- **BlockFilter step=0 崩溃**:如上“三道防线”修复,并处理缓存里持久化为 0 的脏数据。

---

## 国际化(i18n)新增/补充 Key(节选)

- `no_image` / `no_filters_found`
- `param_summary` / `param_summary_default` / `param_summary_changed_count` / `param_summary_reset_hint`
- `clear_filter`

---

## 涉及文件清单(主要)

- `src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/filter/FilterView.kt`
- `src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/filter/FilterListPanel.kt`
- `src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/filter/FilterPreviewArea.kt`
- `src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/filter/FilterAdjustmentPanel.kt`
- `src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/filter/FilterViewModel.kt`
- `src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/filter/FilterParamMeta.kt`
- `src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/filter/FilterParamDefaults.kt`
- `imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/BlockFilter.kt`
- `i18n/src/main/resources/strings/strings_zh.xml`
- `i18n/src/main/resources/strings/strings_en.xml`

---

## 已知未完成项 / 后续建议

- **导出功能**:`FilterTopAppBar` 的 Export 仍为 TODO。
- **日志与可观测性**:当前仍存在少量 `logger.info(...)`(如 FilterView 生命周期/FilterViewModel applyFilter)。建议后续统一降噪或增加 debug 开关。
- **可访问性**:个别 `contentDescription` 仍为英文(如 Zoom In),可按 i18n 统一。

---

## 未来优化方向(路线图建议)

### P0(高收益 / 低风险,建议优先)

- **提交任务的并发与取消策略**  
  - 当前“松手即提交”在用户频繁操作时可能产生提交排队;建议在提交前取消上一次未完成的提交任务,仅保留最后一次松手的提交(类似“last-write-wins”)。
  - 预览任务与提交任务建议分别管理,避免互相 cancel 造成 UI 抖动。

- **预览缓存进一步完善**
  - 目前缓存 key 基于 `identityHashCode(baseImage)`;若后续引入“滤镜叠加链”,可扩展为 `baseImageFingerprint + chainHash + paramsHash`(或至少在 sourceImageOverride 场景下保证 cacheKey 取对)。
  - 可增加简单命中率统计(默认关闭),便于性能回归。

- **枚举型参数的系统化支持**
  - 已对 `ColorFilter.style`、`NatureFilter.style` 做了下拉选择;建议把更多类似参数(如 `gridType`、`waveType` 等)统一纳入 `FilterParamMetaRegistry` 的 `enumOptions`。
  - 枚举项建议改为外部配置(json),降低 Kotlin 侧维护成本。

### P1(高收益 / 中等工程量)

- **非破坏式滤镜栈(专业编辑器体验)**
  - 当前“方式1”支持滤镜叠加,但本质是“破坏式”写回 `currentImage`;建议升级为滤镜栈(A→B→C):
    - UI:支持新增/删除/排序/启用/禁用滤镜条目;
    - 计算:以基线图为输入重算整条链(可配合分段缓存);
    - 历史:一次“应用/确认”生成一个历史节点,或者按滤镜条目粒度记录。
  - 优点:可编辑、可回溯、符合 PS/Lightroom 预期;缺点:需要明确栈的存储与性能策略。

- **参数元数据完全配置化**
  - 将 `FilterParamMeta`(min/max/step/decimals/enumOptions)迁移到 `resources/common/filterParamMeta.json`(或扩展现有 `filterConfig.json`),Kotlin 只保留类型默认兜底与少量安全约束(例如 step>0)。
  - 这样可以由产品/算法侧直接调整范围与枚举定义,不需要改代码。

- **提交与撤销语义更精确**
  - 当前每次松手都会 push 历史;可考虑“合并提交窗口”(例如 500ms 内多次提交合并为一次历史),减少 undo 栈污染。
  - 也可增加“预览模式”开关:只预览不入历史,用户确认后再统一落盘。

### P2(体验增强 / 可持续维护)

- **导出能力落地(与滤镜结果一致)**
  - Export 需要明确导出的是:当前画布效果(含叠加)还是仅某个滤镜结果。
  - 建议支持:导出当前效果 / 导出原图 / 导出带滤镜栈元数据(用于二次编辑)。

- **测试与回归保障**
  - 为关键交互补充自动化验证:列表筛选点击不乱、BlockFilter step 不为 0、枚举下拉可用、缓存命中不串图、清除滤镜恢复正确。

- **可访问性与键盘操作**
  - 下拉/按钮/缩放控件补齐 i18n 的 `contentDescription`;
  - 为参数控件支持键盘上下调整与快捷键(更像桌面编辑器)。




================================================
FILE: docs/layer_render_cache_analysis.md
================================================
# 图层渲染缓存优化分析

## 当前实现分析

### 1. 渲染流程
- `CanvasView` 使用 `collectAsState()` 观察图层列表
- 每次图层变化都会触发 Canvas 重组和重绘
- `LayerRenderer.drawAll()` 遍历所有图层并调用 `render()`
- 每个图层都使用 `drawIntoCanvas` + `saveLayer`,有性能开销

### 2. 性能瓶颈
- **ImageLayer**: 每次重绘都重新计算变换(平移、旋转、缩放)
- **ShapeLayer**: 每次重绘都重新绘制所有形状
- **Canvas 重组**: 图层列表变化时,整个 Canvas 都会重组

## 优化方案对比

### 方案一:路线图中的 ImageBitmap 缓存(不推荐)

**问题**:
1. Compose 的 `DrawScope` 在每次重组时都会重新创建,不能直接缓存 `ImageBitmap`
2. 缓存需要考虑画布尺寸变化(Canvas 尺寸可能变化)
3. 需要考虑透明度、变换等属性的变化
4. 缓存失效逻辑复杂(何时清除缓存?)

**适用场景**:
- 静态图像,尺寸固定
- 不适用于动态变化的图层

### 方案二:Compose 级别的缓存(推荐)⭐

**核心思路**:
1. 使用 `remember` + `key()` 为每个图层创建独立的缓存
2. 使用 `Modifier.drawWithCache` 缓存绘制内容
3. 使用版本号/哈希值标记图层变化

**优势**:
- 利用 Compose 的缓存机制,自动管理生命周期
- 画布尺寸变化时自动失效
- 代码简洁,易于维护

**实现要点**:
```kotlin
@Composable
fun LayerRenderer(
    layers: List<Layer>,
    canvasSize: Size
) {
    layers.forEach { layer ->
        key(layer.id, layer.version) { // 版本号标记变化
            DrawScope.drawWithCache {
                // 缓存绘制内容
                onDrawBehind {
                    layer.render(this)
                }
            }
        }
    }
}
```

### 方案三:分层缓存策略(最佳)⭐⭐

**核心思路**:
1. **ImageLayer**: 缓存变换后的图像(使用 `drawWithCache`)
2. **ShapeLayer**: 使用 `remember` 缓存形状列表,避免重复计算
3. **组合优化**: 只重绘变化的图层区域

**实现要点**:

#### 1. Layer 基类添加版本号
```kotlin
abstract class Layer {
    private var _version by mutableStateOf(0L)
    val version: Long get() = _version
    
    protected fun markDirty() {
        _version++
    }
    
    // 属性变化时调用
    fun updateOpacity(alpha: Float) {
        opacity = alpha.coerceIn(0f, 1f)
        markDirty()
    }
}
```

#### 2. ImageLayer 缓存变换结果
```kotlin
@Composable
fun ImageLayerRenderer(
    layer: ImageLayer,
    canvasSize: Size
) {
    val cachedImage = remember(layer.id, layer.version, canvasSize) {
        // 计算变换后的图像
        renderToBitmap(layer, canvasSize)
    }
    
    Canvas(modifier = Modifier.drawWithCache {
        onDrawBehind {
            drawImage(cachedImage)
        }
    })
}
```

#### 3. ShapeLayer 缓存形状列表
```kotlin
@Composable
fun ShapeLayerRenderer(
    layer: ShapeLayer,
    canvasSize: Size
) {
    val shapes = remember(layer.id, layer.version) {
        // 缓存形状列表,避免重复计算
        layer.getAllShapes()
    }
    
    Canvas(modifier = Modifier) {
        shapes.forEach { shape ->
            drawShape(shape)
        }
    }
}
```

#### 4. LayerRenderer 优化
```kotlin
class LayerRenderer {
    fun drawAll(drawScope: DrawScope, layers: List<Layer>) {
        layers.forEach { layer ->
            if (!layer.visible || layer.opacity <= 0f) return@forEach
            
            // 使用 key 确保只有变化的图层才重绘
            key(layer.id, layer.version) {
                drawLayer(drawScope, layer)
            }
        }
    }
}
```

## 性能提升预估

### 当前性能
- 10 个图层,每次重绘耗时:~50ms
- 拖动图像层时:~16ms/frame(60fps 可能卡顿)

### 优化后性能
- 10 个图层,未变化时:~5ms(使用缓存)
- 拖动图像层时:~8ms/frame(只重绘变化的图层)

**提升**:约 5-10 倍性能提升

## 实施建议

### 阶段一:基础优化(2-3 天)
1. 在 `Layer` 基类添加 `version` 字段
2. 属性变化时调用 `markDirty()`
3. 使用 `key()` 优化 Compose 重组

### 阶段二:ImageLayer 缓存(2-3 天)
1. 使用 `remember` 缓存变换后的图像
2. 使用 `Modifier.drawWithCache` 缓存绘制内容
3. 处理画布尺寸变化

### 阶段三:ShapeLayer 优化(1-2 天)
1. 缓存形状列表
2. 优化形状绘制逻辑

### 阶段四:增量渲染(可选,3-5 天)
1. 只重绘变化的图层区域
2. 使用脏矩形技术

## 注意事项

1. **内存管理**:
   - 缓存会占用内存,需要设置上限
   - 使用 `SoftReference` 或 LRU 缓存

2. **缓存失效**:
   - 画布尺寸变化时自动失效
   - 图层属性变化时手动失效

3. **兼容性**:
   - 确保与现有代码兼容
   - 不影响导出功能

4. **测试**:
   - 测试大量图层场景
   - 测试频繁变化场景
   - 测试内存使用情况

## 结论

**推荐方案**:方案三(分层缓存策略)
- 性能提升明显
- 实现相对简单
- 易于维护和扩展
- 符合 Compose 最佳实践

**预计工作量**:5-7 天(与路线图一致)

**优先级**:中优先级(当前性能可接受,但优化后体验更好)








================================================
FILE: docs/layer_system.md
================================================
# 图层系统概览

本文档记录 Monica 图层系统的最新实现,用于指导后续的功能扩展与维护。

## 核心目标

- 支持图像层与形状层的叠加管理,便于多图层编辑。
- 统一渲染与导出流程,避免重复绘制逻辑。
- 提供直观的 UI 面板,用于图层的增删、排序、锁定与重命名。

## 主要模块

| 模块 | 关键文件 | 功能 |
| --- | --- | --- |
| 图层抽象 | `ui/controlpanel/shapedrawing/layer/Layer.kt` | 统一的图层基类,封装名称、可见性、透明度、锁定状态等属性。 |
| 图层管理 | `ui/controlpanel/shapedrawing/layer/LayerManager.kt` | 负责图层增删改查、排序、激活状态同步,提供监听机制。使用 `StateFlow` 实现响应式更新。 |
| 图像层 | `ui/controlpanel/shapedrawing/layer/ImageLayer.kt` | 保存背景位图及平移、缩放、旋转等变换信息。支持自动适应画布并居中显示。 |
| 形状层 | `ui/controlpanel/shapedrawing/layer/ShapeLayer.kt` | 承载形状绘制数据(线段、矩形、多边形、文本等)。当前限制最多创建 1 个形状层。 |
| 渲染器 | `ui/controlpanel/shapedrawing/layer/LayerRenderer.kt` | 顺序遍历图层并绘制到 Compose `DrawScope`,支持透明度合成。 |
| 控制器 | `ui/controlpanel/shapedrawing/EditorController.kt` | 整合管理器、渲染器、导出流程,并暴露工具切换、图层同步接口。限制形状层数量为 1。导出逻辑内联在控制器中,提供 `exportImageBitmap()` 和 `exportBufferedImage()` 方法。 |

## 工作流

```
用户交互 → EditorController → LayerManager → LayerRenderer → Canvas
                                 ↓
                          导出方法(内联在 EditorController 中)
```

1. UI 侧(例如 `ShapeDrawingView`)通过 `EditorController` 获取或创建图层。
2. 用户绘制的形状实时写入当前激活的 `ShapeLayer`。
3. `CanvasView`(`ui/controlpanel/shapedrawing/widget/CanvasView.kt`)调用 `LayerRenderer.drawAll()` 依次绘制每个图层,并根据透明度应用 `saveLayer`。
4. 导出功能复用渲染器,将所有图层合成为位图或 AWT 图像。

## UI 面板

文件:`ui/controlpanel/shapedrawing/widget/LayerPanel.kt`

- 左侧卡片式列表展示所有图层(顶部为最新图层)。
- 支持:
  - 可见性勾选
  - 锁定/解锁(锁定后图标变红)
  - 重命名(内联编辑)
  - 上移/下移排序
  - 新建形状层(按钮显示"已达上限"当达到限制时)
  - 新建图像层
- 激活的图层使用浅色高亮和边框,提供即时视觉反馈。
- 当前激活的形状层显示"• 当前绘制"标识。

## 关键交互

- **初始化背景层**:`ShapeDrawingView`(`ui/controlpanel/shapedrawing/ShapeDrawingView.kt`)在载入图像时,通过 `LaunchedEffect(imageBitmap)` 从 `LayerManager` 中查找名为"背景图层"的图层,如果不存在则创建,如果存在则更新图像。确保状态同步,避免使用本地状态变量。
- **形状写入**:每次拖动事件结束后,调用 `EditorController.replaceShapesInActiveLayer` 更新层数据。如果形状层已锁定,则禁止写入。在 `onDrag` 和 `onDragEnd` 中都会调用 `syncShapeLayer()` 同步形状数据。
- **图像层拖动**:当激活图层为图像层且未锁定时,可以直接拖动图像层调整位置。拖动时更新 `LayerTransform.translation`,该变换会在自动适应和居中之后应用。
- **导出**:点击保存按钮时,使用 `EditorController.exportBufferedImage` 获取合成结果。导出时使用显示尺寸(`ImageSizeCalculator.getImageDisplayPixelSize`,位于 `ui/widget/image/ImageSizeCalculator.kt`),并考虑 Canvas padding(8.dp),确保导出结果与显示效果一致。

## 设计决策

### 形状层限制
- **限制数量**:当前实现限制最多创建 1 个形状层(`MAX_SHAPE_LAYERS = 1`),简化设计,避免多形状层带来的复杂性。
- **图像层无限制**:支持创建多个图像层,每个图像层可以独立拖动和变换。

### 背景层识别
- **识别方式**:通过图层名称 `"背景图层"` 来识别背景层,而不是通过图像尺寸或其他属性。这种方式更可靠,不受图像尺寸变化影响。
- **渲染差异**:
  - 背景层:只应用自动适应和居中(`fitScale` 和 `centerOffset`),不应用用户定义的变换(`transform.translation`、`transform.rotation`、`transform.scaleX/Y`)。
  - 用户添加的图像层:先应用自动适应和居中,再应用用户定义的变换。这样可以确保图像层在自动适应后,用户还可以进一步调整位置、旋转和缩放。

### 坐标系统
- **统一坐标**:使用显示尺寸(`ImageSizeCalculator.getImageDisplayPixelSize`,位于 `ui/widget/image/ImageSizeCalculator.kt`)作为坐标基准,确保绘制、显示和导出的一致性。导出时也会使用相同的显示尺寸,并减去 Canvas padding(8.dp × 2 = 16.dp),确保导出结果与显示效果完全一致。
- **坐标转换器**:`CoordinateConverter`(`ui/controlpanel/shapedrawing/coordinate/CoordinateConverter.kt`)通过 `remember(state.currentImage, density.density)` 创建,当图像或密度变化时会自动重新计算转换比例,确保坐标转换的准确性。

### 安全保护
- **除零保护**:`ImageLayer.render()` 中在计算缩放比例前检查 `bitmap.width`、`bitmap.height`、`canvasWidth`、`canvasHeight` 是否大于 0,如果任一值为 0 或负数则直接返回,防止除零错误。
- **锁定检查**:
  - 在 `EditorController.addShapeToActiveLayer` 和 `replaceShapesInActiveLayer` 中检查形状层是否锁定,锁定状态下禁止修改。
  - 在 `EditorController.canDrawOnActiveShapeLayer()` 中检查当前激活的形状层是否锁定,用于 UI 交互前的验证。
  - 在 `ShapeDrawingView` 的拖动事件处理中,如果形状层已锁定,会显示提示并阻止绘制操作。

## 测试覆盖

| 测试文件 | 覆盖点 |
| --- | --- |
| `src/jvmTest/kotlin/cn/netdiscovery/monica/editor/layer/LayerManagerTest.kt` | 图层添加、激活同步、排序、清空等行为。 |
| `src/jvmTest/kotlin/cn/netdiscovery/monica/editor/layer/ExportManagerTest.kt` | 图像层合成正确性(导出功能测试,类名为 `EditorControllerExportTest`)。 |

> 当前测试依赖 `kotlin("test")`,位于 `build.gradle.kts` 的 `jvmTest` SourceSet 中。

## 已知问题与修复

### 已修复的问题

1. **除零错误保护**(2024-12)
   - 问题:`ImageLayer.render()` 在 `bitmap.width` 或 `bitmap.height` 为 0 时可能发生除零错误。
   - 修复:添加安全检查,在渲染前验证尺寸有效性。

2. **坐标转换器不更新**(2024-12)
   - 问题:`CoordinateConverter` 使用 `remember` 无依赖项,图像尺寸变化时不更新。
   - 修复:添加 `state.currentImage` 和 `density.density` 作为依赖项。

3. **背景层状态同步**(2024-12)
   - 问题:使用本地 `backgroundLayer` 状态变量(`remember { mutableStateOf<ImageLayer?>(null) }`),与 `LayerManager` 不同步。如果用户通过其他方式修改了背景层,本地状态不会更新。
   - 修复:移除了本地状态变量,改为在 `LaunchedEffect(imageBitmap)` 中直接从 `LayerManager.layers.value` 查找背景层,确保状态一致性。

4. **导出尺寸不一致**(2024-12)
   - 问题:导出时使用原始像素尺寸,与显示尺寸不一致。
   - 修复:导出时使用显示尺寸,并考虑 Canvas padding(8.dp),确保导出结果与显示效果一致。

## 后续待办

- 形状层透明度、混合模式等高级属性。
- 图层拖拽排序(UI 交互层面,当前仅支持上移/下移按钮)。
- 控制器与其它工具模块(涂鸦、滤镜等)的整合策略。
- 渲染性能评估与缓存机制。
- 背景层删除保护(如果未来在 `LayerPanel` 中添加删除功能,需要防止删除背景层)。
- 图像层的旋转和缩放交互(当前仅支持拖动位置)。

> 📋 **详细优化路线图**: 请参考 [图层系统优化路线图](./layer_system_optimization_roadmap.md) 获取完整的优化计划、实施细节和时间估算。

如需扩展新的图层类型,建议:

1. 新建 `Layer` 子类,实现数据结构与 `render()`。
2. 在 `LayerRenderer` 中添加对应的绘制分支。
3. 在 `LayerPanel` 中增加图标、操作项。
4. 补充单元测试覆盖新增逻辑。





================================================
FILE: docs/layer_system_optimization_roadmap.md
================================================
# 图层系统优化路线图

本文档记录 Monica 图层系统的优化方向和实施计划,用于指导后续的功能扩展与性能提升。

**文档版本**: 1.0  
**最后更新**: 2024-12  
**维护者**: Monica 开发团队

---

## 📋 目录

- [一、功能扩展](#一功能扩展)
- [二、性能优化](#二性能优化)
- [三、代码质量提升](#三代码质量提升)
- [四、架构优化](#四架构优化)
- [五、用户体验改进](#五用户体验改进)
- [六、实施计划](#六实施计划)
- [七、技术债务清理](#七技术债务清理)
- [八、监控与评估](#八监控与评估)

---

## 一、功能扩展

### 1.1 UI 交互增强

#### 1.1.1 图层删除功能 ⭐ 高优先级
**当前状态**: `LayerPanel` 中没有删除按钮

**目标**:
- 在图层卡片中添加删除按钮(垃圾桶图标)
- 删除前显示确认对话框
- 防止误删除重要图层

**实施要点**:
```kotlin
// 在 LayerPanel.kt 中添加删除按钮
IconButton(
    onClick = {
        if (layer.name == "背景图层") {
            state.showTray("无法删除背景图层", "提示")
        } else {
            // 显示确认对话框
            showDeleteConfirmDialog = true
        }
    }
) {
    Icon(Icons.Default.Delete, "删除图层")
}
```

**技术细节**:
- 添加背景层删除保护机制
- 删除后自动激活上一个图层
- 支持撤销删除(如果实现撤销/重做功能)

**预计工作量**: 2-3 天

---

#### 1.1.2 拖拽排序 ⭐ 高优先级
**当前状态**: 仅支持上移/下移按钮

**目标**:
- 实现图层卡片拖拽排序
- 提供更直观的交互体验

**实施要点**:
```kotlin
// 使用 Compose 的拖拽 API
Modifier
    .pointerInput(Unit) {
        detectDragGestures { change, dragAmount ->
            // 处理拖拽逻辑
        }
    }
```

**技术细节**:
- 使用 `Modifier.draggable()` 或 `Modifier.pointerInput()` 实现拖拽
- 拖拽时显示视觉反馈(高亮、阴影)
- 拖拽结束后更新图层顺序

**预计工作量**: 3-5 天

---

#### 1.1.3 图层缩略图预览
**当前状态**: 图层卡片仅显示类型图标

**目标**:
- 在图层卡片中显示缩略图
- 提升图层识别度

**实施要点**:
- 为 `ImageLayer` 生成缩略图(缓存)
- 为 `ShapeLayer` 生成预览图
- 使用 `remember` 缓存缩略图,避免重复计算

**预计工作量**: 2-3 天

---

### 1.2 图像层交互增强

#### 1.2.1 旋转和缩放交互 ⭐ 高优先级
**当前状态**: 仅支持拖动位置

**目标**:
- 添加旋转手柄和控制点
- 支持鼠标滚轮缩放
- 支持右键旋转

**实施要点**:
```kotlin
// 在图像层周围添加控制点
data class ImageLayerControls(
    val translation: Offset,
    val rotation: Float,
    val scale: Float,
    val pivot: Offset
)

// 添加交互处理
fun handleImageLayerTransform(
    layerId: UUID,
    transformType: TransformType,
    value: Float
)
```

**技术细节**:
- 在 Canvas 上绘制控制点和旋转手柄
- 检测鼠标悬停和拖动
- 更新 `LayerTransform` 的 `rotation` 和 `scaleX/Y`

**预计工作量**: 5-7 天

---

#### 1.2.2 图像层裁剪
**当前状态**: 不支持裁剪

**目标**:
- 支持裁剪区域选择
- 添加遮罩功能

**实施要点**:
- 在 `ImageLayer` 中添加 `cropRect` 属性
- 渲染时应用裁剪区域
- UI 上显示裁剪控制点

**预计工作量**: 7-10 天

---

### 1.3 形状层功能扩展

#### 1.3.1 解除形状层数量限制(可选)
**当前状态**: 限制最多 1 个形状层(`MAX_SHAPE_LAYERS = 1`)

**目标**:
- 评估是否需要支持多个形状层
- 如需要,重构相关逻辑

**考虑因素**:
- 用户需求是否强烈
- 实现复杂度
- 对现有代码的影响

**预计工作量**: 5-10 天(取决于重构范围)

---

#### 1.3.2 形状层分组
**当前状态**: 不支持分组

**目标**:
- 支持形状分组管理
- 分组级别的可见性/锁定控制

**预计工作量**: 10-15 天

---

## 二、性能优化

### 2.1 渲染性能优化

#### 2.1.1 图层渲染缓存 ⭐ 中优先级
**当前状态**: 每次重绘都重新渲染所有图层

**目标**:
- 对未变化的图层使用缓存
- 减少不必要的重绘

**实施要点**:
```kotlin
class LayerRenderer {
    private val renderCache = mutableMapOf<UUID, ImageBitmap>()
    
    fun drawAll(drawScope: DrawScope, layers: List<Layer>) {
        layers.forEach { layer ->
            if (layer.isDirty) {
                renderCache.remove(layer.id)
                layer.isDirty = false
            }
            
            val cached = renderCache[layer.id]
            if (cached != null && !layer.isDirty) {
                // 使用缓存
                drawScope.drawImage(cached)
            } else {
                // 重新渲染并缓存
                val rendered = renderLayer(layer, drawScope)
                renderCache[layer.id] = rendered
            }
        }
    }
}
```

**技术细节**:
- 在 `Layer` 基类中添加 `isDirty` 标记
- 图层属性变化时设置 `isDirty = true`
- 使用 `remember` 在 Compose 中缓存渲染结果

**预计工作量**: 5-7 天

---

#### 2.1.2 增量渲染
**当前状态**: 所有图层每次都重绘

**目标**:
- 只重绘变化的图层
- 优化 Compose 重组

**实施要点**:
- 使用 `LaunchedEffect` 监听图层变化
- 只更新变化的图层区域
- 使用 `Modifier.drawWithCache` 优化绘制

**预计工作量**: 7-10 天

---

#### 2.1.3 大图像优化
**当前状态**: 可能对超大图像性能不佳

**目标**:
- 对超大图像使用缩略图预览
- 导出时使用全分辨率

**实施要点**:
- 在 `ImageLayer` 中维护缩略图
- 渲染时使用缩略图,导出时使用原图
- 实现渐进式加载

**预计工作量**: 5-7 天

---

### 2.2 内存优化

#### 2.2.1 图像层内存管理
**目标**:
- 实现图像压缩/解压缩策略
- 对不可见图层延迟加载

**实施要点**:
- 使用 `SoftReference` 缓存图像
- 实现 LRU 缓存策略
- 对不可见图层不加载到内存

**预计工作量**: 7-10 天

---

#### 2.2.2 形状数据优化
**目标**:
- 使用更高效的数据结构
- 考虑使用 `Path` 对象缓存

**实施要点**:
- 评估当前 `SnapshotStateMap` 的性能
- 考虑使用 `Path` 对象缓存复杂形状
- 实现形状数据的序列化/反序列化

**预计工作量**: 3-5 天

---

## 三、代码质量提升

### 3.1 测试覆盖

#### 3.1.1 单元测试扩展
**当前状态**: 已有基础测试(`LayerManagerTest.kt`、`ExportManagerTest.kt`)

**目标**:
- 提高测试覆盖率到 80% 以上
- 覆盖边界情况和异常情况

**需要测试的场景**:
- `ImageLayer.render()` 的边界情况(零尺寸、空图像等)
- `LayerManager` 的并发安全测试
- 坐标转换器的各种场景
- 图层变换的数学计算

**预计工作量**: 10-15 天

---

#### 3.1.2 集成测试
**目标**:
- 图层合成导出测试
- UI 交互测试

**实施要点**:
- 使用 Compose 测试框架
- 测试图层操作的完整流程
- 测试导出结果的正确性

**预计工作量**: 7-10 天

---

### 3.2 错误处理

#### 3.2.1 异常处理完善
**目标**:
- 图像加载失败处理
- 渲染错误恢复机制

**实施要点**:
```kotlin
// 在 ImageLayer 中添加错误处理
fun updateImage(newImage: ImageBitmap?) {
    try {
        image = newImage
    } catch (e: Exception) {
        logger.error("更新图像失败", e)
        // 显示错误提示
        // 恢复上一个有效图像
    }
}
```

**预计工作量**: 3-5 天

---

#### 3.2.2 用户反馈改进
**目标**:
- 更明确的错误提示
- 操作成功/失败的 Toast 提示

**实施要点**:
- 统一错误消息格式
- 添加操作成功提示
- 提供错误恢复建议

**预计工作量**: 2-3 天

---

### 3.3 代码重构

#### 3.3.1 背景层管理抽象
**当前状态**: 背景层管理逻辑分散在 `ShapeDrawingView` 中

**目标**:
- 创建 `BackgroundLayerManager` 统一管理背景层

**实施要点**:
```kotlin
class BackgroundLayerManager(
    private val layerManager: LayerManager
) {
    private val BACKGROUND_LAYER_NAME = "背景图层"
    
    fun getOrCreateBackgroundLayer(image: ImageBitmap): ImageLayer {
        val existing = layerManager.layers.value
            .firstOrNull { it.name == BACKGROUND_LAYER_NAME && it is ImageLayer } 
            as? ImageLayer
        
        return existing ?: run {
            val newLayer = ImageLayer(BACKGROUND_LAYER_NAME, image)
            layerManager.addLayer(newLayer, index = 0)
            newLayer
        }
    }
    
    fun updateBackgroundLayer(image: ImageBitmap) {
        val layer = getOrCreateBackgroundLayer(image)
        layer.updateImage(image)
    }
}
```

**预计工作量**: 2-3 天

---

#### 3.3.2 图层操作命令模式
**目标**:
- 实现撤销/重做功能
- 使用命令模式封装图层操作

**实施要点**:
```kotlin
interface LayerCommand {
    fun execute()
    fun undo()
}

class AddLayerCommand(
    private val layerManager: LayerManager,
    private val layer: Layer
) : LayerCommand {
    override fun execute() {
        layerManager.addLayer(layer)
    }
    
    override fun undo() {
        layerManager.removeLayer(layer.id)
    }
}

class CommandManager {
    private val undoStack = mutableListOf<LayerCommand>()
    private val redoStack = mutableListOf<LayerCommand>()
    
    fun execute(command: LayerCommand) {
        command.execute()
        undoStack.add(command)
        redoStack.clear()
    }
    
    fun undo() {
        if (undoStack.isNotEmpty()) {
            val command = undoStack.removeLast()
            command.undo()
            redoStack.add(command)
        }
    }
    
    fun redo() {
        if (redoStack.isNotEmpty()) {
            val command = redoStack.removeLast()
            command.execute()
            undoStack.add(command)
        }
    }
}
```

**预计工作量**: 10-15 天

---

## 四、架构优化

### 4.1 图层类型扩展

#### 4.1.1 文本层(独立图层类型)
**当前状态**: 文本在形状层中

**目标**:
- 创建独立的 `TextLayer` 类型
- 提供更专业的文本编辑功能

**实施要点**:
```kotlin
class TextLayer(
    name: String,
    var text: String = "",
    var font: Font = Font.Default,
    var fontSize: Float = 16f,
    var color: Color = Color.Black,
    var position: Offset = Offset.Zero
) : Layer(
    type = LayerType.TEXT,
    name = name
) {
    override fun render(drawScope: DrawScope) {
        // 文本渲染逻辑
    }
}
```

**预计工作量**: 7-10 天

---

#### 4.1.2 调整层(Adjustment Layer)
**目标**:
- 亮度、对比度、色彩调整
- 不影响原始图像数据

**实施要点**:
```kotlin
class AdjustmentLayer(
    name: String,
    var brightness: Float = 0f,
    var contrast: Float = 1f,
    var saturation: Float = 1f
) : Layer(
    type = LayerType.ADJUSTMENT,
    name = name
) {
    override fun render(drawScope: DrawScope) {
        // 应用调整效果到下层图层
    }
}
```

**预计工作量**: 15-20 天

---

#### 4.1.3 滤镜层
**目标**:
- 模糊、锐化等效果
- 可叠加多个滤镜

**实施要点**:
```kotlin
enum class FilterType {
    BLUR,
    SHARPEN,
    EMBOSS,
    // ...
}

class FilterLayer(
    name: String,
    var filterType: FilterType,
    var intensity: Float = 1f
) : Layer(
    type = LayerType.FILTER,
    name = name
)
```

**预计工作量**: 20-30 天

---

### 4.2 混合模式支持

#### 4.2.1 实现混合模式
**目标**:
- 支持多种混合模式(Normal、Multiply、Screen 等)
- 在图层合成时应用混合模式

**实施要点**:
```kotlin
enum class BlendMode {
    NORMAL,
    MULTIPLY,
    SCREEN,
    OVERLAY,
    SOFT_LIGHT,
    HARD_LIGHT,
    COLOR_DODGE,
    COLOR_BURN,
    DARKEN,
    LIGHTEN,
    DIFFERENCE,
    EXCLUSION
}

class Layer {
    var blendMode: BlendMode = BlendMode.NORMAL
}

// 在 LayerRenderer 中应用混合模式
fun drawAll(drawScope: DrawScope, layers: List<Layer>) {
    layers.forEach { layer ->
        drawScope.drawIntoCanvas { canvas ->
            // 应用混合模式
            val paint = Paint().apply {
                blendMode = when (layer.blendMode) {
                    BlendMode.NORMAL -> BlendMode.SrcOver
                    BlendMode.MULTIPLY -> BlendMode.Multiply
                    // ...
                }
            }
            // 绘制图层
        }
    }
}
```

**预计工作量**: 15-20 天

---

### 4.3 图层组(Layer Group)

#### 4.3.1 实现图层分组
**目标**:
- 支持图层分组
- 组级别的可见性/锁定控制
- 嵌套分组支持

**实施要点**:
```kotlin
class LayerGroup(
    name: String,
    val children: MutableList<Layer> = mutableListOf()
) : Layer(
    type = LayerType.GROUP,
    name = name
) {
    fun addChild(layer: Layer) {
        children.add(layer)
    }
    
    fun removeChild(layerId: UUID) {
        children.removeAll { it.id == layerId }
    }
    
    override fun render(drawScope: DrawScope) {
        if (!visible) return
        children.forEach { child ->
            if (child.visible) {
                child.render(drawScope)
            }
        }
    }
}
```

**预计工作量**: 20-30 天

---

## 五、用户体验改进

### 5.1 快捷键支持 ⭐ 高优先级

#### 5.1.1 实现常用快捷键
**目标**:
- 提供键盘快捷键支持
- 提升操作效率

**快捷键列表**:
- `Ctrl/Cmd + D` - 复制图层
- `Delete` / `Backspace` - 删除图层
- `Ctrl/Cmd + G` - 创建图层组
- `Ctrl/Cmd + Shift + N` - 新建图层
- `Ctrl/Cmd + J` - 复制并新建图层
- `Ctrl/Cmd + Shift + ]` - 图层上移
- `Ctrl/Cmd + Shift + [` - 图层下移
- `Ctrl/Cmd + Z` - 撤销(如果实现)
- `Ctrl/Cmd + Shift + Z` - 重做(如果实现)

**实施要点**:
```kotlin
// 在 ShapeDrawingView 中添加键盘事件处理
Modifier.onKeyEvent { keyEvent ->
    when {
        keyEvent.isCtrlPressed && keyEvent.key == Key.D -> {
            // 复制图层
            true
        }
        keyEvent.key == Key.Delete -> {
            // 删除图层
            true
        }
        else -> false
    }
}
```

**预计工作量**: 5-7 天

---

### 5.2 图层搜索/过滤

#### 5.2.1 实现搜索功能
**目标**:
- 按名称搜索图层
- 按类型过滤
- 显示/隐藏空图层

**实施要点**:
```kotlin
@Composable
fun LayerPanel(
    editorController: EditorController,
    state: ApplicationState,
    modifier: Modifier = Modifier
) {
    var searchQuery by remember { mutableStateOf("") }
    var filterType by remember { mutableStateOf<LayerType?>(null) }
    
    val filteredLayers = remember(layers, searchQuery, filterType) {
        layers.filter { layer ->
            (searchQuery.isEmpty() || layer.name.contains(searchQuery, ignoreCase = true)) &&
            (filterType == null || layer.type == filterType)
        }
    }
    
    // UI 实现
}
```

**预计工作量**: 3-5 天

---

### 5.3 批量操作

#### 5.3.1 实现多选功能
**目标**:
- 支持多选图层
- 批量锁定/解锁
- 批量重命名

**实施要点**:
```kotlin
class LayerManager {
    private val _selectedLayers = MutableStateFlow<Set<UUID>>(emptySet())
    val selectedLayers: StateFlow<Set<UUID>> = _selectedLayers.asStateFlow()
    
    fun selectLayer(layerId: UUID, multiSelect: Boolean = false) {
        if (multiSelect) {
            _selectedLayers.value = _selectedLayers.value.toMutableSet().apply {
                if (contains(layerId)) remove(layerId) else add(layerId)
            }
        } else {
            _selectedLayers.value = setOf(layerId)
        }
    }
    
    fun batchLock(locked: Boolean) {
        _selectedLayers.value.forEach { id ->
            setLayerLocked(id, locked)
        }
    }
}
```

**预计工作量**: 7-10 天

---

## 六、实施计划

### 6.1 短期计划(1-2 周)

**优先级**: ⭐⭐⭐ 最高

1. **图层删除功能**(2-3 天)
   - 添加删除按钮
   - 实现背景层保护
   - 添加确认对话框

2. **拖拽排序**(3-5 天)
   - 实现拖拽交互
   - 更新图层顺序

3. **错误处理完善**(2-3 天)
   - 完善异常处理
   - 改进用户提示

**总工作量**: 7-11 天

---

### 6.2 中期计划(1-2 月)

**优先级**: ⭐⭐ 高

1. **图像层旋转/缩放交互**(5-7 天)
   - 添加控制点
   - 实现交互逻辑

2. **渲染性能优化**(5-7 天)
   - 实现渲染缓存
   - 优化重绘逻辑

3. **撤销/重做功能**(10-15 天)
   - 实现命令模式
   - 添加撤销/重做 UI

4. **快捷键支持**(5-7 天)
   - 实现常用快捷键
   - 添加快捷键提示

**总工作量**: 25-36 天

---

### 6.3 长期计划(3-6 月)

**优先级**: ⭐ 中

1. **混合模式支持**(15-20 天)
   - 实现各种混合模式
   - 添加 UI 选择器

2. **图层组功能**(20-30 天)
   - 实现分组逻辑
   - 添加分组 UI

3. **调整层和滤镜层**(35-50 天)
   - 实现调整层
   - 实现滤镜层
   - 添加效果预览

4. **测试覆盖扩展**(10-15 天)
   - 扩展单元测试
   - 添加集成测试

**总工作量**: 80-115 天

---

## 七、技术债务清理

### 7.1 代码清理

#### 7.1.1 移除未使用的代码
- 检查是否有废弃的 API
- 清理注释掉的代码
- 移除未使用的导入

**预计工作量**: 1-2 天

---

#### 7.1.2 文档完善
- 添加 API 文档(KDoc)
- 创建使用示例
- 编写架构决策记录(ADR)

**预计工作量**: 3-5 天

---

#### 7.1.3 代码规范
- 统一命名规范
- 代码格式化
- 添加必要的注释

**预计工作量**: 2-3 天

---

### 7.2 依赖管理

#### 7.2.1 依赖更新
- 定期更新依赖版本
- 评估新版本的功能和性能改进
- 处理废弃的 API

**预计工作量**: 持续进行

---

## 八、监控与评估

### 8.1 性能监控

#### 8.1.1 关键指标
- **渲染帧率**: 目标 60 FPS
- **内存使用**: 监控峰值内存
- **导出耗时**: 目标 < 2 秒(普通图像)

**实施要点**:
```kotlin
// 添加性能监控
class PerformanceMonitor {
    fun measureRenderTime(block: () -> Unit): Long {
        val start = System.currentTimeMillis()
        block()
        return System.currentTimeMillis() - start
    }
    
    fun logMemoryUsage() {
        val runtime = Runtime.getRuntime()
        val used = runtime.totalMemory() - runtime.freeMemory()
        logger.info("内存使用: ${used / 1024 / 1024} MB")
    }
}
```

---

### 8.2 用户反馈

#### 8.2.1 反馈收集
- 收集使用痛点
- 功能需求优先级
- Bug 报告

**实施要点**:
- 在应用中添加反馈入口
- 定期收集用户意见
- 建立需求优先级评估机制

---

### 8.3 代码质量指标

#### 8.3.1 质量指标
- **测试覆盖率**: 目标 80% 以上
- **代码复杂度**: 使用工具分析(如 SonarQube)
- **技术债务**: 定期评估和清理

**实施要点**:
- 使用代码质量工具
- 定期代码审查
- 技术债务跟踪

---

## 九、风险评估

### 9.1 技术风险

1. **性能风险**
   - 大量图层可能导致性能下降
   - **缓解措施**: 实现渲染缓存和增量渲染

2. **兼容性风险**
   - 新功能可能影响现有功能
   - **缓解措施**: 充分测试,渐进式发布

3. **复杂度风险**
   - 功能增加可能导致代码复杂度上升
   - **缓解措施**: 代码重构,模块化设计

---

### 9.2 时间风险

1. **估算不准确**
   - 实际工作量可能超过估算
   - **缓解措施**: 预留缓冲时间,分阶段实施

2. **优先级冲突**
   - 多个高优先级任务可能冲突
   - **缓解措施**: 明确优先级,合理分配资源

---

## 十、总结

本路线图提供了图层系统优化的全面规划,涵盖了功能扩展、性能优化、代码质量提升、架构优化、用户体验改进等多个方面。

**建议实施顺序**:
1. 先完成短期计划(1-2 周),快速提升用户体验
2. 然后进行中期计划(1-2 月),优化性能和添加核心功能
3. 最后推进长期计划(3-6 月),实现高级功能和架构优化

**关键成功因素**:
- 持续的用户反馈收集
- 定期的性能监控和优化
- 代码质量保证(测试、文档、规范)
- 渐进式实施,避免大范围重构

---

**文档维护**: 本文档应随着项目进展定期更新,记录实际完成情况、遇到的问题和调整的计划。



================================================
FILE: domain/build.gradle.kts
================================================
plugins {
    kotlin("jvm")
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(kotlin("test"))
    implementation ("org.jetbrains.kotlin:kotlin-stdlib")
}

tasks.test {
    useJUnitPlatform()
}
kotlin {
    jvmToolchain(17)
}

================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/ColorCorrectionSettings.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.domain.ColorCorrectionSettings
 * @author: Tony Shen
 * @date: 2024/11/6 10:40
 * @version: V1.0 <描述当前版本功能>
 */
data class ColorCorrectionSettings(
    val contrast:Int = 255,     // 对比度,范围 0-510
    val hue:Int = 180,          // 色调,范围 0-360
    val saturation:Int = 255,   // 饱和度,范围 0-510
    val lightness:Int = 255,    // 亮度,范围 0-510
    val temperature:Int = 255,  // 色温,范围 0-510
    val highlight:Int = 255,    // 高光,范围 0-510
    val shadow:Int = 255,       // 阴影,范围 0-510
    val sharpen:Int = 0,        // 锐化,范围 0-255
    val corner:Int = 0,         // 暗角,范围 0-255

    val status:Int = 0 // 1 contrast, 2 hue, 3 saturation, 4 lightness, 5 temperature, 6 highlight, 7 shadow, 8 sharpen, 9 corner
)

================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/ContourDisplaySettings.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.ui.controlpanel.ai.experiment.model.ContourDisplaySettings
 * @author: Tony Shen
 * @date: 2024/10/29 14:26
 * @version: V1.0 <描述当前版本功能>
 */
data class ContourDisplaySettings(
    var showOriginalImage: Boolean = false,
    var showBoundingRect: Boolean = false,
    var showMinAreaRect: Boolean = false,
    var showCenter: Boolean = false
)

================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/ContourFilterSettings.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.ui.controlpanel.ai.experiment.model.ContourFilterSettings
 * @author: Tony Shen
 * @date: 2024/10/29 17:52
 * @version: V1.0 <描述当前版本功能>
 */
data class ContourFilterSettings (
    var minPerimeter:Double = 0.0,
    var maxPerimeter:Double = 0.0,

    var minArea:Double = 0.0,
    var maxArea:Double = 0.0,

    var minRoundness:Double = 0.0,
    var maxRoundness:Double = 0.0,

    var minAspectRatio:Double = 0.0,
    var maxAspectRatio:Double = 0.0
)

================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/DecodedPreviewImage.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.domain.DecodedPreviewImage
 * @author: Tony Shen
 * @date: 2025/7/21 12:40
 * @version: V1.0 <描述当前版本功能>
 */
data class DecodedPreviewImage(
    val nativePtr: Long,  // 对应 MonicaImageProcess 中 PyramidImage 对象的指针地址
    val width: Int,
    val height: Int,
    val previewImage: IntArray // 返回金字塔第一层的图像
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as DecodedPreviewImage

        if (nativePtr != other.nativePtr) return false
        if (width != other.width) return false
        if (height != other.height) return false
        if (!previewImage.contentEquals(other.previewImage)) return false

        return true
    }

    override fun hashCode(): Int {
        var result = nativePtr.hashCode()
        result = 31 * result + width
        result = 31 * result + height
        result = 31 * result + previewImage.contentHashCode()
        return result
    }
}


================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/GeneralSettings.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.domain.GeneralSettings
 * @author: Tony Shen
 * @date: 2025/2/7 10:27
 * @version: V1.0 <描述当前版本功能>
 */
data class GeneralSettings(
    var outputBoxR: Int,
    var outputBoxG: Int,
    var outputBoxB: Int,
    var size: Int,
    var maxHistorySize: Int,
    var deepSeekApiKey: String,
    var geminiApiKey: String,
    var algorithmUrl: String,
    var themeId: String = "LIGHT"
)

================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/MatchTemplateSettings.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.ui.controlpanel.ai.experiment.model.MatchTemplateSettings
 * @author: Tony Shen
 * @date: 2025/1/4 20:29
 * @version: V1.0 <描述当前版本功能>
 */
data class MatchTemplateSettings (
    var matchType:Int = 0,                   // 0 表示原图匹配,1 表示灰度匹配 2 表示边缘匹配
    var angleStart:Int = 0,
    var angleEnd:Int = 360,
    var angleStep:Int = 10,
    var scaleStart:Double = 0.0,
    var scaleEnd:Double = 1.0,
    var scaleStep:Double = 0.1,
    var matchTemplateThreshold:Double = 0.8, // 模版匹配的阈值
    var scoreThreshold: Float = 0.6f ,       // 置信分数的阈值(nms 相关)
    var nmsThreshold: Float = 0.3f           // 非极大值抑制的阈值(nms 相关)
)

================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/MorphologicalOperationSettings.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.ui.controlpanel.ai.experiment.model.MorphologicalOperationSettings
 * @author: Tony Shen
 * @date: 2024/12/26 20:42
 * @version: V1.0 <描述当前版本功能>
 */
data class MorphologicalOperationSettings(
    var op:Int = 0,
    var shape:Int = 0,
    var width:Int = 0,
    var height:Int = 0
)

================================================
FILE: domain/src/main/kotlin/cn/netdiscovery/monica/domain/NativeImage.kt
================================================
package cn.netdiscovery.monica.domain

/**
 *
 * @FileName:
 *          cn.netdiscovery.monica.domain.NativeImage
 * @author: Tony Shen
 * @date: 2025/7/22 14:20
 * @version: V1.0 <描述当前版本功能>
 */
data class NativeImage(
    val width: Int,
    val height: Int,
    val pixels: IntArray
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as NativeImage

        if (width != other.width) return false
        if (height != other.height) return false
        if (!pixels.contentEquals(other.pixels)) return false

        return true
    }

    override fun hashCode(): Int {
        var result = width
        result = 31 * result + height
        result = 31 * result + pixels.contentHashCode()
        return result
    }
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

================================================
FILE: gradle.properties
================================================
kotlin.code.style=official
app.version=1.1.5
kotlin.version=2.1.0
agp.version=7.3.0
compose.version=1.6.11
kotlinx.coroutines.core.version=1.8.1-Beta
koin.compose=4.0.0

logback=1.2.3
colormath=3.5.0
twelvemonkeys=3.12.0
batik=1.19

rxcache=2.2.0
coroutines.utils=v1.1.8

================================================
FILE: gradlew
================================================
#!/bin/sh

#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

##############################################################################
#
#   Gradle start up script for POSIX generated by Gradle.
#
#   Important for running:
#
#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
#       noncompliant, but you have some other compliant shell such as ksh or
#       bash, then to run this script, type that shell name before the whole
#       command line, like:
#
#           ksh Gradle
#
#       Busybox and similar reduced shells will NOT work, because this script
#       requires all of these POSIX shell features:
#         * functions;
#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
#         * compound commands having a testable exit status, especially «case»;
#         * various built-in commands including «command», «set», and «ulimit».
#
#   Important for patching:
#
#   (2) This script targets any POSIX shell, so it avoids extensions provided
#       by Bash, Ksh, etc; in particular arrays are avoided.
#
#       The "traditional" practice of packing multiple parameters into a
#       space-separated string is a well documented source of bugs and security
#       problems, so this is (mostly) avoided, by progressively accumulating
#       options in "$@", and eventually passing that to Java.
#
#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
#       see the in-line comments for details.
#
#       There are tweaks for specific operating systems such as AIX, CygWin,
#       Darwin, MinGW, and NonStop.
#
#   (3) This script is generated from the Groovy template
#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
#       within the Gradle project.
#
#       You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: $0 may be a link
app_path=$0

# Need this for daisy-chained symlinks.
while
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
    [ -h "$app_path" ]
do
    ls=$( ls -ld "$app_path" )
    link=${ls#*' -> '}
    case $link in             #(
      /*)   app_path=$link ;; #(
      *)    app_path=$APP_HOME$link ;;
    esac
done

APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
    echo "$*"
} >&2

die () {
    echo
    echo "$*"
    echo
    exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in                #(
  CYGWIN* )         cygwin=true  ;; #(
  Darwin* )         darwin=true  ;; #(
  MSYS* | MINGW* )  msys=true    ;; #(
  NONSTOP* )        nonstop=true ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD=$JAVA_HOME/jre/sh/java
    else
        JAVACMD=$JAVA_HOME/bin/java
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD=java
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
    case $MAX_FD in #(
      max*)
        MAX_FD=$( ulimit -H -n ) ||
            warn "Could not query maximum file descriptor limit"
    esac
    case $MAX_FD in  #(
      '' | soft) :;; #(
      *)
        ulimit -n "$MAX_FD" ||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
    esac
fi

# Collect all arguments for the java command, stacking in reverse order:
#   * args from the command line
#   * the main class name
#   * -classpath
#   * -D...appname settings
#   * --module-path (only if needed)
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

    JAVACMD=$( cygpath --unix "$JAVACMD" )

    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    for arg do
        if
            case $arg in                                #(
              -*)   false ;;                            # don't mess with options #(
              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
                    [ -e "$t" ] ;;                      #(
              *)    false ;;
            esac
        then
            arg=$( cygpath --path --ignore --mixed "$arg" )
        fi
        # Roll the args list around exactly as many times as the number of
        # args, so each arg winds up back in the position where it started, but
        # possibly modified.
        #
        # NB: a `for` loop captures its iteration list before it begins, so
        # changing the positional parameters here affects neither the number of
        # iterations, nor the values presented in `arg`.
        shift                   # remove old arg
        set -- "$@" "$arg"      # push replacement arg
    done
fi

# Collect all arguments for the java command;
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
#     shell script including quotes and variable substitutions, so put them in
#     double quotes to make sure that they get re-expanded; and
#   * put everything else in single quotes, so that it's not re-expanded.

set -- \
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
        -classpath "$CLASSPATH" \
        org.gradle.wrapper.GradleWrapperMain \
        "$@"

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
#   set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- $(
        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
        xargs -n1 |
        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
        tr '\n' ' '
    )" '"$@"'

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: i18n/QUICK_REFERENCE.md
================================================
# i18n 脚本快速参考

## 🚀 快速开始

### 基本检查
```bash
# 检查中英文文件是否同步
./string_manager.sh -m

# 检查重复项(不修改文件)
./string_manager.sh -c -d

# 检查行号一致性
./position_check.sh
```

### 修复重复项
```bash
# 自动修复重复项
./string_manager.sh -c -f
```

## 📋 常用命令

| 命令 | 功能 |
|------|------|
| `./string_manager.sh --help` | 显示帮助信息 |
| `./string_manager.sh -m` | 比对中英文文件差异 |
| `./string_manager.sh -c -d` | 检查重复项(不修改) |
| `./string_manager.sh -c -f` | 自动修复重复项 |
| `./string_manager.sh -c -v -d` | 详细检查重复项 |
| `./string_manager.sh -a -f` | 全功能模式 |
| `./position_check.sh` | 检查行号一致性 |

## ⚡ 一键检查脚本

创建一个检查脚本 `quick_check.sh`:

```bash
#!/bin/bash
echo "=== i18n 文件快速检查 ==="
echo ""

echo "1. 检查中英文文件同步性..."
./string_manager.sh -m
echo ""

echo "2. 检查重复项..."
./string_manager.sh -c -d
echo ""

echo "3. 检查行号一致性..."
./position_check.sh
echo ""

echo "=== 检查完成 ==="
```

使用方法:
```bash
chmod +x quick_check.sh
./quick_check.sh
```

## 🔧 故障排除

### 权限问题
```bash
chmod +x string_manager.sh position_check.sh
```

### 文件不存在
```bash
ls -la src/main/resources/strings/
```

### 语法检查
```bash
bash -n string_manager.sh
bash -n position_check.sh
```

## 📊 输出解读

### ✅ 正常状态
- 中英文文件字符串数量相同
- 无缺失翻译
- 无重复字符串名称

### ⚠️ 需要注意
- 有重复字符串内容(正常,不同key可以有相同内容)
- 行号不一致(正常,文件结构可能不同)

### ❌ 需要修复
- 有重复字符串名称
- 有缺失的翻译
- 文件不同步


================================================
FILE: i18n/README.md
================================================
# 🌍 Monica 国际化工具集

## 📋 概述

Monica 项目的国际化字符串资源管理工具集,提供完整的字符串资源文件管理解决方案。

## 🛠️ 工具列表

### 核心工具
- **`string_manager.sh`** - 综合管理工具
  - 清理重复项
  - 检查缺失翻译
  - 文件统计报告
  - 自动修复功能

- **`position_check.sh`** - 位置比对工具
  - 检查中英文文件位置一致性
  - 详细的位置差异分析
  - 统计信息报告

### 文档
- **`SCRIPT_USAGE_GUIDE.md`** - 脚本使用指南
  - 详细的使用说明和示例
  - 所有选项和参数说明
  - 输出解读和故障排除
- **`QUICK_REFERENCE.md`** - 快速参考
  - 常用命令速查
  - 一键检查脚本
  - 快速故障排除
- **`I18N_STRING_MANAGEMENT_GUIDE.md`** - 完整使用指南
  - 详细的使用说明
  - 工作流程指导
  - 最佳实践建议
  - 故障排除指南

## 🚀 快速开始

```bash
# 进入工具目录
cd i18n

# 一键检查所有问题
./quick_check.sh

# 或者单独使用各个工具
./string_manager.sh -m    # 检查同步性
./string_manager.sh -c -d # 检查重复项
./position_check.sh       # 检查行号一致性
```

## 📖 详细文档

- **`SCRIPT_USAGE_GUIDE.md`** - 脚本使用详细指南
- **`QUICK_REFERENCE.md`** - 快速参考和常用命令
- **`I18N_STRING_MANAGEMENT_GUIDE.md`** - 完整的国际化管理指南

## 🎯 主要功能

- ✅ 自动检测重复字符串
- ✅ 比对中英文翻译完整性
- ✅ 检查文件位置一致性
- ✅ 生成详细统计报告
- ✅ 自动备份和修复
- ✅ 支持批量处理

## 📊 当前状态

- **字符串总数**: 366个
- **文件状态**: 完整同步
- **重复项**: 无重复字符串名称
- **位置一致性**: 349个key位置不一致(正常现象)

## 🔧 维护建议

1. **日常**: 使用 `./quick_check.sh` 快速检查
2. **开发新功能后**: 运行 `./string_manager.sh -m` 检查同步性
3. **发现重复项**: 使用 `./string_manager.sh -c -f` 自动修复
4. **定期维护**: 每月运行完整检查

---

**版本**: 2.0  
**最后更新**: 2025-09-03  
**维护者**: AI Assistant


================================================
FILE: i18n/SCRIPT_USAGE_GUIDE.md
================================================
# i18n 脚本使用指南

## 📖 概述

本指南介绍 i18n 模块中两个字符串资源管理脚本的使用方法:
- `string_manager.sh` - 字符串资源文件综合管理工具
- `position_check.sh` - 位置比对脚本

## 🔧 string_manager.sh - 字符串资源文件综合管理工具

### 功能特性

- ✅ 检查重复的字符串名称和内容
- ✅ 比对中英文文件差异
- ✅ 自动修复重复项
- ✅ 位置比对模式
- ✅ 详细输出和干运行模式
- ✅ 自动备份功能

### 基本语法

```bash
./string_manager.sh [选项] <文件路径>
```

### 模式选项

| 选项 | 长选项 | 功能 |
|------|--------|------|
| `-c` | `--cleanup` | 清理模式:检查并清理重复项 |
| `-m` | `--compare` | 比对模式:比对中英文文件差异 |
| `-p` | `--position` | 位置比对模式:检查相同key的行号是否一致 |
| `-a` | `--all` | 全功能模式:清理 + 比对 |

### 清理模式选项

| 选项 | 长选项 | 功能 |
|------|--------|------|
| `-v` | `--verbose` | 详细输出 |
| `-d` | `--dry-run` | 只检查,不修改文件 |
| `-n` | `--no-backup` | 不创建备份文件 |
| `-f` | `--auto-fix` | 自动修复重复项(保留第一次出现的版本) |

### 使用示例

#### 1. 查看帮助信息
```bash
./string_manager.sh --help
```

#### 2. 比对中英文文件差异
```bash
# 检查中英文文件是否同步
./string_manager.sh -m
```

**输出示例:**
```
=== 中英文字符串资源文件比对 ===

1. 提取中文文件中的字符串名称...
2. 提取英文文件中的字符串名称...
3. 统计信息...
中文文件字符串数量:      366
英文文件字符串数量:      366

4. 中文有但英文没有的字符串:
✅ 没有缺失的英文翻译

5. 英文有但中文没有的字符串:
✅ 没有缺失的中文翻译

=== 比对完成 ===
```

#### 3. 检查重复项(不修改文件)
```bash
# 干运行模式,只检查不修改
./string_manager.sh -c -d
```

**输出示例:**
```
字符串资源文件清理工具 v2.0

处理文件: src/main/resources/strings/strings_zh.xml
==================================
=== 文件统计报告 ===
文件: src/main/resources/strings/strings_zh.xml
总行数:           470
字符串总数: 366
唯一字符串名称:           366
重复字符串名称:        0

✓ 没有发现重复的字符串名称
发现 31 个重复的字符串内容:
人脸替换
人脸检测
伽马变换
...
处理完成!
```

#### 4. 自动修复重复项
```bash
# 自动修复重复项,保留第一次出现的版本
./string_manager.sh -c -f
```

#### 5. 详细检查模式
```bash
# 详细输出,显示重复项的详细信息
./string_manager.sh -c -v -d
```

#### 6. 全功能模式
```bash
# 清理 + 比对,自动修复
./string_manager.sh -a -f
```

#### 7. 检查特定文件
```bash
# 检查指定的文件
./string_manager.sh -c src/main/resources/strings/strings_zh.xml
```

### 输出说明

#### 文件统计报告
- **总行数**: 文件的总行数
- **字符串总数**: 包含的字符串定义数量
- **唯一字符串名称**: 不重复的字符串名称数量
- **重复字符串名称**: 重复的字符串名称数量

#### 重复项检测
- **重复字符串名称**: 相同 `name` 属性的字符串
- **重复字符串内容**: 相同内容的字符串(可能名称不同)

## 🔍 position_check.sh - 位置比对脚本

### 功能特性

- ✅ 检查中英文配置文件中相同key的行号是否一致
- ✅ 统计行号差异信息
- ✅ 提供详细的差异报告

### 基本语法

```bash
./position_check.sh
```

### 使用示例

#### 检查行号一致性
```bash
./position_check.sh
```

**输出示例:**
```
=== 中英文字符串资源文件位置比对 ===

1. 提取中文文件中的key和行号...
2. 提取英文文件中的key和行号...
3. 统计信息...
中文文件字符串数量: 366
英文文件字符串数量: 366

4. 共同key数量: 366

5. 检查行号一致性...
行号不匹配: adaptive_threshold_algorithm
  中文文件第288行
  英文文件第435行
  差异: -147 行

行号不匹配: adaptive_threshold_cancelled
  中文文件第446行
  英文文件第421行
  差异: 25 行

...

6. 位置比对结果:
⚠️  发现      349 个key的行号不一致
  最大行号差异: 443 行
  平均行号差异: 53.3 行

=== 位置比对完成 ===
```

### 输出说明

#### 统计信息
- **中文文件字符串数量**: 中文文件中的字符串总数
- **英文文件字符串数量**: 英文文件中的字符串总数
- **共同key数量**: 两个文件都包含的字符串数量

#### 行号差异
- **行号不匹配**: 相同key在不同文件中的行号不同
- **差异**: 行号差值(正数表示中文文件行号更大,负数表示英文文件行号更大)

#### 比对结果
- **不匹配数量**: 行号不一致的key数量
- **最大行号差异**: 最大的行号差值
- **平均行号差异**: 平均的行号差值

## 🚀 常见使用场景

### 场景1:日常维护检查
```bash
# 检查文件是否同步
./string_manager.sh -m

# 检查是否有重复项
./string_manager.sh -c -d
```

### 场景2:修复重复项
```bash
# 先检查重复项
./string_manager.sh -c -v -d

# 确认后自动修复
./string_manager.sh -c -f
```

### 场景3:全面检查
```bash
# 全功能检查
./string_manager.sh -a -f

# 检查行号一致性
./position_check.sh
```

### 场景4:开发新功能后
```bash
# 添加新字符串后,检查同步性
./string_manager.sh -m

# 检查是否有重复
./string_manager.sh -c -d
```

## ⚠️ 注意事项

### 备份建议
- 使用 `-f` 选项自动修复前,建议先运行 `-d` 选项检查
- 脚本会自动创建备份文件(除非使用 `-n` 选项)
- 备份文件格式:`原文件名.backup.时间戳`

### 文件路径
- 脚本默认使用 `src/main/resources/strings/` 目录下的文件
- 可以指定其他文件路径作为参数
- 确保文件路径正确且文件存在

### 权限要求
- 脚本需要执行权限:`chmod +x string_manager.sh position_check.sh`
- 修改文件需要写入权限

## 🔧 故障排除

### 常见错误

#### 1. 权限错误
```bash
# 解决方案:添加执行权限
chmod +x string_manager.sh position_check.sh
```

#### 2. 文件不存在
```bash
# 检查文件路径
ls -la src/main/resources/strings/
```

#### 3. 语法错误
```bash
# 检查脚本语法
bash -n string_manager.sh
bash -n position_check.sh
```

### 调试技巧

#### 1. 详细输出
```bash
# 使用 -v 选项查看详细信息
./string_manager.sh -c -v -d
```

#### 2. 干运行模式
```bash
# 使用 -d 选项不修改文件
./string_manager.sh -c -d
```

#### 3. 检查特定文件
```bash
# 指定文件路径
./string_manager.sh -c /path/to/your/file.xml
```

## 📚 相关文档

- [i18n 模块 README](README.md)
- [字符串管理指南](I18N_STRING_MANAGEMENT_GUIDE.md)
- [国际化测试文档](src/test/kotlin/cn/netdiscovery/monica/i18n/InternationalizationTest.kt)

## 🤝 贡献指南

如果你发现脚本的问题或有改进建议,请:

1. 检查脚本语法:`bash -n script_name.sh`
2. 测试功能:使用 `-d` 选项进行干运行
3. 提交问题或改进建议

---

**最后更新**: 2024年12月
**版本**: 2.0
**维护者**: AI Assistant


================================================
FILE: i18n/build.gradle.kts
================================================
plugins {
    kotlin("jvm")
}

repositories {
    mavenCentral()
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

dependencies {
    testImplementation(kotlin("test"))
    testImplementation("junit:junit:4.13.2")
    implementation ("org.jetbrains.kotlin:kotlin-stdlib")

    // log config
    implementation("ch.qos.logback:logback-classic:${rootProject.extra["logback"]}")
    implementation("ch.qos.logback:logback-core:${rootProject.extra["logback"]}")
    implementation("ch.qos.logback:logback-access:${rootProject.extra["logback"]}")
    
    // Config module
    implementation(project(":config"))
}

tasks.test {
    useJUnitPlatform()
}

================================================
FILE: i18n/position_check.sh
================================================
#!/bin/bash

# 位置比对脚本
# 检查中英文配置文件中相同key的行号是否一致

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 文件路径
ZH_FILE="src/main/resources/strings/strings_zh.xml"
EN_FILE="src/main/resources/strings/strings_en.xml"

echo "=== 中英文字符串资源文件位置比对 ==="
echo ""

# 检查文件是否存在
if [[ ! -f "$ZH_FILE" ]]; then
    echo -e "${RED}错误: 中文文件不存在: $ZH_FILE${NC}"
    exit 1
fi

if [[ ! -f "$EN_FILE" ]]; then
    echo -e "${RED}错误: 英文文件不存在: $EN_FILE${NC}"
    exit 1
fi

# 创建临时文件存储key和行号的映射
ZH_TEMP=$(mktemp)
EN_TEMP=$(mktemp)

echo "1. 提取中文文件中的key和行号..."
grep -n 'name="[^"]*"' "$ZH_FILE" | sed 's/^\([0-9]*\):.*name="\([^"]*\)".*/\1:\2/' > "$ZH_TEMP"

echo "2. 提取英文文件中的key和行号..."
grep -n 'name="[^"]*"' "$EN_FILE" | sed 's/^\([0-9]*\):.*name="\([^"]*\)".*/\1:\2/' > "$EN_TEMP"

echo "3. 统计信息..."
ZH_COUNT=$(wc -l < "$ZH_TEMP")
EN_COUNT=$(wc -l < "$EN_TEMP")
echo "中文文件字符串数量: $ZH_COUNT"
echo "英文文件字符串数量: $EN_COUNT"
echo ""

# 找出共同的key
COMMON_KEYS=$(comm -12 <(cut -d: -f2 "$ZH_TEMP" | sort) <(cut -d: -f2 "$EN_TEMP" | sort))
COMMON_COUNT=$(echo "$COMMON_KEYS" | wc -l)
echo "4. 共同key数量: $COMMON_COUNT"
echo ""

# 检查行号差异
echo "5. 检查行号一致性..."
MISMATCHED_COUNT=0
TOTAL_DIFF=0
MAX_DIFF=0

echo "$COMMON_KEYS" | while read key; do
    ZH_LINE=$(grep ":$key$" "$ZH_TEMP" | cut -d: -f1)
    EN_LINE=$(grep ":$key$" "$EN_TEMP" | cut -d: -f1)
    
    if [[ -n "$ZH_LINE" && -n "$EN_LINE" ]]; then
        DIFF=$((ZH_LINE - EN_LINE))
        # 取绝对值
        if [[ $DIFF -lt 0 ]]; then
            ABS_DIFF=$((-DIFF))
        else
            ABS_DIFF=$DIFF
        fi
        
        if [[ $ABS_DIFF -gt 0 ]]; then
            echo -e "${YELLOW}行号不匹配: $key${NC}"
            echo "  中文文件第${ZH_LINE}行"
            echo "  英文文件第${EN_LINE}行"
            echo "  差异: $DIFF 行"
            echo ""
        fi
    fi
done

# 统计不匹配的数量
ACTUAL_MISMATCHED=$(echo "$COMMON_KEYS" | while read key; do
    ZH_LINE=$(grep ":$key$" "$ZH_TEMP" | cut -d: -f1)
    EN_LINE=$(grep ":$key$" "$EN_TEMP" | cut -d: -f1)
    
    if [[ -n "$ZH_LINE" && -n "$EN_LINE" ]]; then
        DIFF=$((ZH_LINE - EN_LINE))
        # 取绝对值
        if [[ $DIFF -lt 0 ]]; then
            ABS_DIFF=$((-DIFF))
        else
            ABS_DIFF=$DIFF
        fi
        
        if [[ $ABS_DIFF -gt 0 ]]; then
            echo "1"
        fi
    fi
done | wc -l)

# 计算总差异
ACTUAL_TOTAL_DIFF=$(echo "$COMMON_KEYS" | while read key; do
    ZH_LINE=$(grep ":$key$" "$ZH_TEMP" | cut -d: -f1)
    EN_LINE=$(grep ":$key$" "$EN_TEMP" | cut -d: -f1)
    
    if [[ -n "$ZH_LINE" && -n "$EN_LINE" ]]; then
        DIFF=$((ZH_LINE - EN_LINE))
        # 取绝对值
        if [[ $DIFF -lt 0 ]]; then
            ABS_DIFF=$((-DIFF))
        else
            ABS_DIFF=$DIFF
        fi
        echo "$ABS_DIFF"
    fi
done | awk '{sum+=$1} END {print sum}')

# 计算最大差异
ACTUAL_MAX_DIFF=$(echo "$COMMON_KEYS" | while read key; do
    ZH_LINE=$(grep ":$key$" "$ZH_TEMP" | cut -d: -f1)
    EN_LINE=$(grep ":$key$" "$EN_TEMP" | cut -d: -f1)
    
    if [[ -n "$ZH_LINE" && -n "$EN_LINE" ]]; then
        DIFF=$((ZH_LINE - EN_LINE))
        # 取绝对值
        if [[ $DIFF -lt 0 ]]; then
            ABS_DIFF=$((-DIFF))
        else
            ABS_DIFF=$DIFF
        fi
        echo "$ABS_DIFF"
    fi
done | sort -n | tail -1)

echo "6. 位置比对结果:"
if [[ $ACTUAL_MISMATCHED -eq 0 ]]; then
    echo -e "${GREEN}✅ 所有共同key的行号都一致!${NC}"
else
    echo -e "${YELLOW}⚠️  发现 $ACTUAL_MISMATCHED 个key的行号不一致${NC}"
    echo "  最大行号差异: $ACTUAL_MAX_DIFF 行"
    if [[ $ACTUAL_MISMATCHED -gt 0 ]]; then
        AVERAGE_DIFF=$(echo "scale=1; $ACTUAL_TOTAL_DIFF / $ACTUAL_MISMATCHED" | bc 2>/dev/null || echo "N/A")
        echo "  平均行号差异: $AVERAGE_DIFF 行"
    fi
fi

# 清理临时文件
rm "$ZH_TEMP" "$EN_TEMP"

echo ""
echo "=== 位置比对完成 ==="

================================================
FILE: i18n/quick_check.sh
================================================
#!/bin/bash

# i18n 文件快速检查脚本
# 功能:一键检查中英文文件同步性、重复项和行号一致性

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

echo -e "${BLUE}=== i18n 文件快速检查 ===${NC}"
echo ""

# 检查脚本是否存在
if [[ ! -f "string_manager.sh" ]]; then
    echo -e "${RED}错误: string_manager.sh 不存在${NC}"
    exit 1
fi

if [[ ! -f "position_check.sh" ]]; then
    echo -e "${RED}错误: position_check.sh 不存在${NC}"
    exit 1
fi

# 检查脚本权限
if [[ ! -x "string_manager.sh" ]]; then
    echo -e "${YELLOW}警告: string_manager.sh 没有执行权限,正在修复...${NC}"
    chmod +x string_manager.sh
fi

if [[ ! -x "position_check.sh" ]]; then
    echo -e "${YELLOW}警告: position_check.sh 没有执行权限,正在修复...${NC}"
    chmod +x position_check.sh
fi

echo -e "${BLUE}1. 检查中英文文件同步性...${NC}"
echo "----------------------------------------"
./string_manager.sh -m
echo ""

echo -e "${BLUE}2. 检查重复项...${NC}"
echo "----------------------------------------"
./string_manager.sh -c -d
echo ""

echo -e "${BLUE}3. 检查行号一致性...${NC}"
echo "----------------------------------------"
./position_check.sh
echo ""

echo -e "${GREEN}=== 检查完成 ===${NC}"
echo ""
echo -e "${YELLOW}💡 提示:${NC}"
echo "  - 如果发现重复项,使用: ./string_manager.sh -c -f"
echo "  - 如果发现缺失翻译,请手动添加"
echo "  - 行号不一致是正常的,不影响功能"
echo ""
echo -e "${BLUE}📚 更多信息请查看: SCRIPT_USAGE_GUIDE.md${NC}"


================================================
FILE: i18n/src/main/kotlin/cn/netdiscovery/monica/i18n/Language.kt
================================================
package cn.netdiscovery.monica.i18n

/**
 * 支持的语言枚举
 */
enum class Language(val code: String, val displayName: String, val flag: String) {
    CHINESE("zh", "中文", "🇨🇳"),
    ENGLISH("en", "English", "🇺🇸");
    
    companion object {
        fun fromCode(code: String): Language {
            return values().find { it.code == code } ?: CHINESE
        }
        
        fun getSystemLanguage(): Language {
            val systemLang = java.util.Locale.getDefault().language
            return when (systemLang) {
                "zh" -> CHINESE
                "en" -> ENGLISH
                else -> CHINESE // 默认中文,因为项目主要面向中文用户
            }
        }
    }
}


================================================
FILE: i18n/src/main/kotlin/cn/netdiscovery/monica/i18n/LocalizationManager.kt
================================================
package cn.netdiscovery.monica.i18n

import cn.netdiscovery.monica.config.category.ConfigCategoryManager

/**
 * 国际化管理器
 *
 * 负责管理应用的语言设置和本地化资源
 */
object LocalizationManager {
    private const val LANGUAGE_KEY = "selected_language"

    // 当前语言状态
    private var _currentLanguage = getSavedLanguage()
    val currentLanguage: Language
        get() = _currentLanguage

    // 语言变化监听器列表
    private val languageChangeListeners = mutableListOf<() -> Unit>()

    /**
     * 添加语言变化监听器
     */
    fun addLanguageChangeListener(listener: () -> Unit) {
        languageChangeListeners.add(listener)
    }

    /**
     * 移除语言变化监听器
     */
    fun removeLanguageChangeListener(listener: () -> Unit) {
        languageChangeListeners.remove(listener)
    }

    /**
     * 获取保存的语言设置
     */
    private fun getSavedLanguage(): Language {
        val savedCode: String? = ConfigCategoryManager.load(LANGUAGE_KEY, null as String?)
        return if (savedCode != null) {
            Language.fromCode(savedCode)
        } else {
            Language.getSystemLanguage()
        }
    }

    /**
     * 设置当前语言
     */
    fun setLanguage(language: Language) {
        if (_currentLanguage != language) {
            _currentLanguage = language
            ConfigCategoryManager.save(LANGUAGE_KEY, language.code)
            // 清除缓存,强制重新加载资源
            clearCache()
            // 通知所有监听器语言已变化
            languageChangeListeners.forEach { it.invoke() }
        }
    }

    /**
     * 清除资源缓存
     */
    private fun clearCache() {
        chineseXmlResource = null
        englishXmlResource = null
    }

    // XML资源缓存
    private var chineseXmlResource: XmlBasedStringResource? = null
    private var englishXmlResource: XmlBasedStringResource? = null

    /**
     * 获取XML字符串资源
     */
    fun getXmlResource(language: Language): XmlBasedStringResource {
        return when (language) {
            Language.CHINESE -> {
                if (chineseXmlResource == null) {
                    chineseXmlResource = XmlBasedStringResource(Language.CHINESE)
                }
                chineseXmlResource ?: throw IllegalStateException("中文资源文件未加载")
            }
            Language.ENGLISH -> {
                if (englishXmlResource == null) {
                    englishXmlResource = XmlBasedStringResource(Language.ENGLISH)
                }
                englishXmlResource ?: throw IllegalStateException("英文资源文件未加载")
            }
        }
    }

    /**
     * 获取当前语言的字符串资源
     */
    fun getString(key: String): String {
        val xmlResource = getXmlResource(_currentLanguage)
        return xmlResource.get(key)
    }

    /**
     * 获取带参数的字符串资源
     */
    fun getString(key: String, vararg args: Any): String {
        val xmlResource = getXmlResource(_currentLanguage)
        return xmlResource.get(key, *args)
    }

    /**
     * 获取所有支持的语言
     */
    fun getSupportedLanguages(): List<Language> = Language.values().toList()

    /**
     * 获取当前语言代码
     */
    fun getCurrentLanguageCode(): String = _currentLanguage.code

    /**
     * 获取当前语言显示名称
     */
    fun getCurrentLanguageDisplayName(): String = _currentLanguage.displayName
}

/**
 * 获取当前语言的字符串资源
 */
fun getCurrentStringResource(): StringResource {
    return StringResource(LocalizationManager.currentLanguage)
}

/**
 * 字符串资源访问器
 */
class StringResource(private val language: Language) {
    private val xmlResource by lazy { LocalizationManager.getXmlResource(language) }

    fun get(key: String): String = LocalizationManager.getString(key)
    fun get(key: String, vararg args: Any): String = LocalizationManager.getString(key, *args)

    /**
     * 直接从XML资源获取字符串(用于测试和调试)
     */
    fun getFromXml(key: String): String = xmlResource.get(key)

    /**
     * 检查XML资源中是否包含指定key
     */
    fun containsInXml(key: String): Boolean = xmlResource.contains(key)

    /**
     * 获取XML资源信息
     */
    fun getXmlResourceInfo(): String = xmlResource.getResourceInfo()

    /**
     * 获取所有可用的键
     */
    fun getAllKeys(): Set<String> = xmlResource.getAllKeys()
}


================================================
FILE: i18n/src/main/kotlin/cn/netdiscovery/monica/i18n/XmlStringResource.kt
================================================
package cn.netdiscovery.monica.i18n

import org.w3c.dom.Document
import org.w3c.dom.Element
import java.io.InputStream
import org.slf4j.LoggerFactory
import javax.xml.parsers.DocumentBuilderFactory

/**
 * XML格式的字符串资源加载器
 * 
 * 支持从XML文件加载国际化字符串资源
 */
object XmlStringResource {
    private val logger = LoggerFactory.getLogger(XmlStringResource::class.java.name)
    
    /**
     * 从XML文件加载字符串资源
     */
    fun loadStrings(resourcePath: String): Map<String, String> {
        return try {
            val inputStream: InputStream? = this::class.java.classLoader.getResourceAsStream(resourcePath)
            if (inputStream == null) {
                logger.warn("无法找到资源文件: $resourcePath")
                return emptyMap()
            }
            
            val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
            val document: Document = documentBuilder.parse(inputStream)
            
            val stringMap = mutableMapOf<String, String>()
            val stringNodes = document.getElementsByTagName("string")
            
            for (i in 0 until stringNodes.length) {
                val stringNode = stringNodes.item(i) as Element
                val name = stringNode.getAttribute("name")
                val value = stringNode.textContent
                
                if (name.isNotEmpty() && value.isNotEmpty()) {
                    stringMap[name] = value
                } else {
                    logger.warn("跳过无效的字符串资源: name='$name', value='$value'")
                }
            }
            
            logger.info("成功加载 ${stringMap.size} 个字符串资源从: $resourcePath")
            stringMap
            
        } catch (e: Exception) {
            logger.error("加载XML字符串资源失败: $resourcePath, 错误: ${e.message}")
            e.printStackTrace()
            emptyMap()
        }
    }
    
    /**
     * 获取指定语言的字符串资源
     */
    fun getStringsForLanguage(language: Language): Map<String, String> {
        val resourcePath = when (language) {
            Language.CHINESE -> "strings/strings_zh.xml"
            Language.ENGLISH -> "strings/strings_en.xml"
        }
        
        return loadStrings(resourcePath)
    }
}

/**
 * 基于XML的字符串资源实现
 */
class XmlBasedStringResource(
    private val language: Language
) {
    
    private val strings: Map<String, String> = XmlStringResource.getStringsForLanguage(language)
    private val logger = LoggerFactory.getLogger(XmlBasedStringResource::class.java.name)
    
    fun get(key: String): String {
        val value = strings[key]
        if (value == null) {
            logger.warn("未找到字符串资源: $key (语言: ${language.name})")
            return "[$key]" // 返回带方括号的key作为fallback
        }
        return value
    }
    
    /**
     * 获取带参数替换的字符串
     */
    fun get(key: String, vararg args: Any): String {
        val template = get(key)
        return try {
            String.format(template, *args)
        } catch (e: Exception) {
            logger.warn("字符串格式化失败: $key, 模板: '$template', 参数: ${args.contentToString()}")
            template
        }
    }
    
    /**
     * 检查是否包含指定的key
     */
    fun contains(key: String): Boolean {
        return strings.containsKey(key)
    }
    
    /**
     * 获取所有可用的keys
     */
    fun getAllKeys(): Set<String> {
        return strings.keys
    }
    
    /**
     * 获取资源统计信息
     */
    fun getResourceInfo(): String {
        return "语言: ${language.name}, 字符串数量: ${strings.size}"
    }
}


================================================
FILE: i18n/src/main/resources/strings/strings_en.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <!-- Main window and menu -->
    <string name="app_name">Monica</string>
    <string name="app_description">Monica is a cross-platform image editor</string>
    <string name="software_version_info">Software Version Info</string>
    <string name="open_local_image">Open Local Image</string>
    <string name="load_network_image">Load Network Image</string>
    <string name="save_image">Save Image</string>
    <string name="screenshot_full_screen">Screenshot (Full Screen)</string>
    <string name="screenshot_area">Screenshot (Area Selection)</string>
    <string name="web_screenshot">Web Long Screenshot</string>
    <string name="exit">Exit</string>
    
    <!-- Control panel -->
    <string name="control_panel">Control Panel</string>
    <string name="general_settings">General Settings</string>
    <string name="basic_functions">Basic Functions</string>
    <string name="color_correction">Color Correction</string>
    <string name="filter_effects">Filter Effects</string>
    <string name="ai_laboratory">AI Laboratory</string>
    
    <!-- General settings -->
    <string name="settings">Settings</string>
    <string name="monica_general_settings">Monica General Settings</string>
    <string name="update">Update</string>
    <string name="close">Close</string>
    
    <!-- Basic functions -->
    <string name="image_blur">Image Blur</string>
    <string name="image_mosaic">Image Mosaic</string>
    <string name="image_doodle">Image Doodle</string>
    <string name="shape_drawing">Shape Drawing</string>
    <string name="color_picker">Color Picker</string>
    <string name="crop_image">Crop Image</string>
    <string name="image_compression">Image Compression</string>
    <string name="original">Original</string>
    <string name="compressed">Compressed</string>
    <string name="input_selection">Input Selection</string>
    <string name="single_image">Single Image</string>
    <string name="batch_folder">Folder (Batch)</string>
    <string name="generate_gif">Generate GIF</string>
    
    <!-- Image Compression Related -->
    <string name="compression_mode">Compression Mode</string>
    <string name="compression_algorithm">Compression Algorithm</string>
    <string name="quality_setting">Quality Setting (Visually Lossless)</string>
    <string name="quality_description">Higher values result in lower compression rates and larger files; lower values result in higher compression rates but may lose details</string>
    <string name="compression_level">Compression Level</string>
    <string name="compression_level_description">Higher levels result in higher compression rates but longer processing times; lower levels are faster to process</string>
    <string name="compress_image">Compress Image</string>
    <string name="select_input_folder">Select Input Folder</string>
    <string name="select_output_folder">Select Output Folder</string>
    <string name="start_batch_compression">Start Batch Compression</string>
    <string name="compression_result">Compression Result</string>
    <string name="original_size">Original Size</string>
    <string name="compressed_size">Compressed Size</string>
    <string name="compression_ratio">Compression Ratio</string>
    <string name="size_increase">Increase</string>
    <string name="compressed_file_larger_warning">Tip: The compressed file is larger than the original. Try lowering quality or choosing another algorithm (e.g., JPEG Quality for photos; PNG Optimization for screenshots/transparency).</string>
    <string name="selected">Selected</string>
    <string name="error_please_select_image">Error: Please select an image first</string>
    <string name="compressing_image">Compressing image...</string>
    <string name="webp_not_supported">WebP format is not supported on this system, automatically converted to %s</string>
    <string name="webp_encode_failed">WebP encoding failed, automatically converted to %s</string>
    <string name="compression_success">Compression successful!</string>
    <string name="compression_failed">Compression failed</string>
    <string name="compression_error">Compression error: %s</string>
    <string name="preparing_batch_compression">Preparing batch compression...</string>
    <string name="no_images_in_folder">No images in folder</string>
    <string name="compression_cancelled">Compression cancelled</string>
    <string name="compressing_file">Compressing: %s (%d/%d)</string>
    <string name="cannot_read_image">Cannot read image</string>
    <string name="batch_compression_completed">Batch compression completed (Success: %d/%d)</string>
    <string name="batch_compression_error">Batch compression error: %s</string>
    <string name="please_select_image_to_compress">Please select an image to compress</string>
    <string name="select_image">Select Image</string>
    <string name="please_select_or_load_image">Please select or load an image first</string>
    <string name="file_overwrite_confirm">File Overwrite Confirmation</string>
    <string name="file_exists_overwrite">File \"%s\" already exists. Overwrite?</string>
    <string name="save_compressed_image">Save Compressed Image</string>
    <string name="please_compress_first">Please compress first</string>
    <string name="start_compression">Start Compression</string>
    <string name="please_select_image_in_preview">Please select an image in the preview area first</string>
    <string name="batch_mode_no_preview">Batch mode does not support preview comparison. Please check progress and summary.</string>
    <string name="reset">Reset</string>
    <string name="applied_to_editor">Applied to editor</string>
    <string name="apply_to_editor">Apply to Editor</string>
    <string name="save_success">Save successful: %s</string>
    <string name="save_failed">Save failed</string>
    <string name="save">Save</string>
    <string name="cannot_read_image_file">Cannot read image file</string>
    <string name="load_image_failed">Failed to load image: %s</string>
    <string name="image_cleared">Image cleared</string>
    <string name="clear_image">Clear image</string>
    <string name="webp_not_supported_auto_convert">WebP is not supported on this system, will automatically convert to %s</string>
    <string name="batch_compression_warning">Batch Compression Warning</string>
    <string name="output_folder_has_files">Output folder already contains %d files. Batch compression may overwrite files with the same name. Continue?</string>
    <string name="cancel">Cancel</string>
    <string name="undo">Undo</string>
    <string name="undo_not_available">Nothing to undo</string>
    <string name="undo_success">Undone</string>
    <string name="undo_and_reset_success">Undone and reset</string>
    <string name="reset_done">Reset</string>
    <string name="format_conversion_warning_jpg_to_png">Note: Converting JPG to PNG may result in a larger file size, as PNG is a lossless format</string>
    <string name="format_conversion_warning_jpg_to_webp_lossless">Note: Converting JPG to WebP Lossless may result in a larger file size</string>
    
    <!-- Shape drawing -->
    <string name="select_color">Select Color</string>
    <string name="change_properties">Change Properties</string>
    <string name="line">Line</string>
    <string name="circle">Circle</string>
    <string name="triangle">Triangle</string>
    <string name="rectangle">Rectangle</string>
    <string name="polygon">Polygon</string>
    <string name="add_text">Add Text</string>
    <string name="save">Save</string>
    
    <!-- Doodle function -->
    <string name="brush">Brush</string>
    <string name="eraser">Eraser</string>
    <string name="clear">Clear</string>
    
    <!-- Color correction -->
    <string name="natural_language_color">Natural Language Color</string>
    <string name="enter_color_instruction">Enter color instruction</string>
    <string name="color_parameters_updated">Parameters updated: %s</string>
    
    <!-- Filter effects -->
    <string name="filter_parameters">Filter Parameters</string>
    
    <!-- AI Laboratory -->
    <string name="ai_laboratory_description">AI Laboratory</string>
    <string name="simple_cv_algorithm">Simple CV Algorithm Quick Validation</string>
    <string name="face_detection">Face Detection</string>
    <string name="generate_sketch">Generate Sketch</string>
    <string name="face_swap">Face Swap</string>
    <string name="anime_style">Anime Style</string>
    
    <!-- AI experiment pages -->
    <string name="home">Home</string>
    <string name="binary_image">Binary Image</string>
    <string name="edge_detection">Edge Detection</string>
    <string name="contour_analysis">Contour Analysis</string>
    <string name="image_enhance">Image Enhancement</string>
    <string name="image_denoising">Image Denoising</string>
    <string name="morphological_operations">Morphological Operations</string>
    <string name="match_template">Template Matching</string>
    <string name="parameter_history">Parameter History</string>
    
    <!-- Crop function -->
    <string name="crop_type">Crop Type</string>
    <string name="content_scale">Content Scale</string>
    <string name="aspect_ratio">Aspect Ratio</string>
    <string name="crop_frame">Crop Frame</string>
    <string name="crop_properties_settings">Crop Properties Settings</string>
    <string name="confirm_crop">Confirm</string>
    <string name="dismiss_crop">Dismiss</string>
    
    <!-- Dialogs and prompts -->
    <string name="loading">Loading...</string>
    <string name="error">Error</string>
    <string name="success">Success</string>
    <string name="warning">Warning</string>
    <string name="info">Info</string>
    
    <!-- Property settings -->
    <string name="alpha">Alpha</string>
    <string name="font_size">Font Size</string>
    <string name="fill">Fill</string>
    <string name="border">Border</string>
    <string name="stroke_width">Stroke Width</string>
    
    <!-- Network image loading -->
    <string name="load_network_image_dialog">Load Network Image</string>
    <string name="enter_image_url">Enter image URL</string>
    <string name="load">Load</string>
    <string name="url_invalid">Invalid URL format</string>

    <!-- Theme Settings -->
    <string name="basic_settings">Basic Settings</string>
    <string name="api_settings">API Settings</string>
    <string name="theme_settings">Theme Settings</string>
    <string name="language_settings">Language Settings</string>
    <string name="current_theme">Current Theme</string>
    <string name="select_theme">Select Theme</string>
    <string name="theme_light">Light Theme</string>
    <string name="theme_dark">Dark Theme</string>
    <string name="theme_blue">Blue Theme</string>
    <string name="theme_green">Green Theme</string>
    <string name="theme_purple">Purple Theme</string>
    <string name="theme_orange">Orange Theme</string>
    <string name="theme_pink">Pink Theme</string>
    <string name="reset_to_default_theme">Reset to Default Theme</string>
    
    <!-- Version info -->
    <string name="version_info">Version Info</string>
    <string name="copyright">© 2024 Tony Shen. All rights reserved.</string>
    
    <!-- Status and messages -->
    <string name="basic_function_cancelled">Basic functions cancelled</string>
    <string name="basic_function_selected">Basic functions selected</string>
    <string name="general_settings_cancelled">General settings cancelled</string>
    <string name="general_settings_selected">General settings selected</string>
    <string name="color_correction_cancelled">Color correction cancelled</string>
    <string name="color_correction_selected">Color correction selected</string>
    <string name="filter_cancelled">Filter effects cancelled</string>
    <string name="filter_selected">Filter effects selected</string>
    <string name="ai_laboratory_cancelled">AI laboratory cancelled</string>
    <string name="ai_laboratory_selected">AI laboratory selected</string>
    
    <!-- Tooltips -->
    <string name="simple_cv_tooltip">Simple CV Algorithm Quick Validation</string>
    <string name="face_detect_tooltip">Face Detection</string>
    <string name="sketch_drawing_tooltip">Generate Sketch</string>
    <string name="face_swap_tooltip">Face Swap</string>
    <string name="anime_style_tooltip">Anime Style</string>
    
    <!-- Version info -->
    <string name="monica_software_info">Monica Software Information</string>
    <string name="monica_version_info">Monica Version: %s, %s, Build Time: %s</string>
    <string name="opencv_version_info">OpenCV Version: %s, Local Algorithm Library: %s</string>
    <string name="copyright_info">Copyright: Copyright 2024-Present, Tony Shen</string>
    <string name="github_url">Github URL: https://github.com/fengzhizi715/Monica</string>
    
    <!-- Settings related -->
    <string name="output_box_color_settings">Output Box Color Settings:</string>
    <string name="area_size_settings">Area Size Settings (for blur/mosaic):</string>
    <string name="max_history_size">Max History Size per Module:</string>
    <string name="algorithm_service_url">Algorithm Service URL:</string>
    <string name="init_filter_params">Initialize filter parameter configuration</string>
    <string name="clear_cache_data">Clear all cache data</string>
    <string name="reset_to_system_language">Reset</string>
    
    <!-- Basic functions -->
    <string name="x_direction">X Direction</string>
    <string name="y_direction">Y Direction</string>
    
    <!-- Filter effects -->
    <string name="enter_filter">Enter Filter Interface</string>
    <string name="select_filter">Please select a filter</string>
    <string name="filter_name">%s Filter</string>
    <string name="select_filter_first">Please select a filter first</string>
    <string name="image_editor_filter_module">Image Editor: Filter Module</string>
    <string name="export">Export</string>
    <string name="adjustments">Adjustments</string>
    <string name="reset_filter">Reset Filter</string>
    <string name="apply">Apply</string>
    <string name="failed_to_apply_filter">Failed to apply filter</string>
    <string name="filter_applied">Filter applied</string>
    <string name="no_image">No image</string>
    <string name="no_filters_found">No filters found</string>
    <string name="param_summary">Parameter Summary</string>
    <string name="param_summary_default">Using default parameters</string>
    <string name="param_summary_changed_count">%d adjusted</string>
    <string name="param_summary_reset_hint">Tip: click "Reset Filter" at the bottom to restore default parameters</string>
    <string name="clear_filter">Clear Filter</string>
    <!-- ColorFilter style options -->
    <string name="color_filter_style_0">Autumn</string>
    <string name="color_filter_style_1">Bone</string>
    <string name="color_filter_style_2">Cool</string>
    <string name="color_filter_style_3">Warm</string>
    <string name="color_filter_style_4">HSV</string>
    <string name="color_filter_style_5">Jet</string>
    <string name="color_filter_style_6">Ocean</string>
    <string name="color_filter_style_7">Pink</string>
    <string name="color_filter_style_8">Rainbow</string>
    <string name="color_filter_style_9">Spring</string>
    <string name="color_filter_style_10">Summer</string>
    <string name="color_filter_style_11">Winter</string>
    <!-- NatureFilter style options -->
    <string name="nature_filter_style_1">Atmosphere</string>
    <string name="nature_filter_style_2">Burning</string>
    <string name="nature_filter_style_3">Haze</string>
    <string name="nature_filter_style_4">Frozen</string>
    <string name="nature_filter_style_5">Lava</string>
    <string name="nature_filter_style_6">Metal</string>
    <string name="nature_filter_style_7">Ocean</string>
    <string name="nature_filter_style_8">Flowing Water</string>
    <string name="notes">Notes:</string>
    <string name="search">Search</string>
    <string name="fit">Fit</string>
    
    <!-- Anime style -->
    <string name="select_anime_style">Please select an anime style</string>
    
    <!-- GIF generation -->
    <string name="add_image_first">Please add images first</string>
    <string name="select_images">Select images</string>
    <string name="gif_generation_strategy">GIF Generation Strategy</string>
    <string name="gif_width">GIF Width</string>
    <string name="gif_height">GIF Height</string>
    <string name="frame_interval">Frame Interval (ms)</string>
    <string name="loop_playback">Loop Playback</string>
    
    <!-- Natural language color correction -->
    <string name="natural_language_color_correction">Natural Language Color Correction</string>
    
    <!-- AI experiment related -->
    <string name="gaussian_filter">Gaussian Filter</string>
    <string name="median_filter">Median Filter</string>
    <string name="gaussian_bilateral_filter">Gaussian Bilateral Filter</string>
    <string name="mean_shift_filter">Mean Shift Filter</string>
    <string name="grayscale_image">Grayscale Image</string>
    <string name="threshold_segmentation">Threshold Segmentation</string>
    <string name="canny_edge_detection">Canny Edge Detection</string>
    <string name="color_image_segmentation">Color Image Segmentation</string>
    <string name="template">Template</string>
    <string name="matching_method">Matching Method</string>
    <string name="rotation">Rotation</string>
    <string name="min_angle">Min Angle</string>
    <string name="max_angle">Max Angle</string>
    <string name="angle_step">Angle Step</string>
    <string name="scale">Scale</string>
    <string name="min_scale">Min Scale</string>
    <string name="max_scale">Max Scale</string>
    <string name="scale_step">Scale Step</string>
    <string name="template_matching_params">Template Matching Parameters</string>
    <string name="threshold">Threshold</string>
    <string name="nms_params">NMS Parameters</string>
    <string name="score_threshold">Score Threshold</string>
    <string name="nms_threshold">NMS Threshold</string>
    <string name="filter_settings">Filter Settings</string>
    <string name="min_perimeter">Min Value</string>
    <string name="max_perimeter">Max Value</string>
    <string name="min_area">Min Value</string>
    <string name="max_area">Max Value</string>
    <string name="min_roundness">Min Value</string>
    <string name="max_roundness">Max Value</string>
    <string name="min_aspect_ratio">Min Value</string>
    <string name="max_aspect_ratio">Max Value</string>
    <string name="display_settings">Display Settings</string>
    <string name="operation_element">Operation Element</string>
    <string name="structural_element">Structural Element</string>
    <string name="width">Width</string>
    <string name="height">Height</string>
    <string name="histogram_equalization">Histogram Equalization</string>
    <string name="clahe">Contrast Limited Adaptive Histogram Equalization (CLAHE)</string>
    <string name="gamma_transform">Gamma Transform</string>
    <string name="laplace_sharpening">Laplace Sharpening</string>
    <string name="usm_sharpening">USM Sharpening</string>
    <string name="automatic_color_balance">Automatic Color Balance</string>
    <string name="edge_detection_operator">Edge Detection Operator</string>
    
    <!-- Face swap -->
    <string name="replace_target_face_count">Replace face count in target</string>
    
    <!-- Common prompts -->
    <string name="click_to_select_image">Click to select image</string>
    
    <!-- Window titles -->
    <string name="monica_image_editor">Monica Image Editor</string>
    <string name="notification">Notification</string>
    <string name="export_image">Export Image</string>
    
    <!-- Common labels -->
    <string name="min_value">Min Value</string>
    <string name="max_value">Max Value</string>
    
    <string name="restore">Restore</string>
    <string name="restore_original">Restore Original</string>
    <string name="previous_step">Previous Step</string>
    <string name="enlarge_preview">Enlarge Preview</string>
    <string name="delete">Delete</string>
    
    <!-- Image operations -->
    <string name="image_color_correction">Image Color Correction</string>
    <string name="enter_image_color_correction">Enter Image Color Correction Interface</string>
    <string name="image_flip">Image Flip</string>
    <string name="image_rotate">Image Rotate</string>
    <string name="image_scale">Image Scale</string>
    <string name="image_shear">Image Shear</string>
    <string name="image_crop">Image Crop</string>
    
    <!-- Anime styles -->
    <string name="miyazaki_style">Miyazaki Style</string>
    <string name="japanese_portrait_style">Japanese Portrait Style</string>
    <string name="black_white_line_art">Black &amp; White Line Art</string>
    <string name="shinkai_style">Shinkai Style</string>
    <string name="cute_style">Cute Style</string>
    
    <!-- Algorithm service -->
    <string name="image_grayscale">Image Grayscale</string>
    <string name="threshold_type">Threshold Type</string>
    
    <!-- Edge detection operators -->
    <string name="prewitt_operator">Prewitt Operator</string>
    <string name="sobel_operator">Sobel Operator</string>
    <string name="log_operator">LoG Operator</string>
    <string name="dog_operator">DoG Operator</string>
    <string name="first_derivative_operator">First Derivative Operator</string>
    <string name="second_derivative_operator">Second Derivative Operator</string>
    <string name="first_derivative_edge_detection">First Derivative Edge Detection</string>
    <string name="second_derivative_edge_detection">Second Derivative Edge Detection</string>
    <string name="canny_operator">Canny Operator</string>
    <string name="canny_edge_detection_full">Canny Edge Detection</string>
    
    <!-- Prompt messages -->
    <string name="click_to_select_image_or_drag">Click to select image or drag image here</string>
    <string name="image_save_success">Image saved successfully</string>
    <string name="image_save_failed">Image save failed</string>
    <string name="please_select_first_derivative_operator">Please select first derivative operator type</string>
    <string name="please_select_second_derivative_operator">Please select second derivative operator type</string>
    <string name="please_select_threshold_type">Please select threshold type</string>
    <string name="please_select_global_threshold_segmentation">Please select global threshold segmentation type</string>
    <string name="please_select_adaptive_threshold_algorithm">Please select adaptive threshold algorithm type</string>
    <string name="please_select_threshold_type_and_segmentation">Please select threshold type and global threshold segmentation or adaptive threshold segmentation</string>
    
    <!-- Parameter validation errors -->
    <string name="ksize_needs_int">ksize needs int type</string>
    <string name="sigma_x_needs_double">sigmaX needs double type</string>
    <string name="sigma_y_needs_double">sigmaY needs double type</string>
    <string name="d_needs_int">d needs int type</string>
    <string name="sigma_color_needs_double">sigmaColor needs double type</string>
    <string name="sigma_space_needs_double">sigmaSpace needs double type</string>
    <string name="sp_needs_double">sp needs double type</string>
    <string name="sr_needs_double">sr needs double type</string>
    <string name="width_needs_int">width needs int type</string>
    <string name="height_needs_int">height needs int type</string>
    <string name="x_direction_needs_float">x direction needs float type</string>
    <string name="y_direction_needs_float">y direction needs float type</string>
    <string name="block_size_needs_int">blockSize needs int type</string>
    <string name="c_needs_int">c needs int type</string>
    <string name="threshold1_needs_double">threshold1 needs double type</string>
    <string name="threshold2_needs_double">threshold2 needs double type</string>
    <string name="aperture_size_needs_int">apertureSize needs int type</string>
    <string name="hmin_needs_int">hmin needs int type</string>
    <string name="smin_needs_int">smin needs int type</string>
    <string name="vmin_needs_int">vmin needs int type</string>
    <string name="hmax_needs_int">hmax needs int type</string>
    <string name="smax_needs_int">smax needs int type</string>
    <string name="vmax_needs_int">vmax needs int type</string>
    
    <!-- Additional parameter validation errors -->
    <string name="sigma1_needs_double">sigma1 needs double type</string>
    <string name="sigma2_needs_double">sigma2 needs double type</string>
    <string name="size_needs_int">size needs int type</string>
    
    <!-- Natural language color correction -->
    <string name="update_parameters">🤖 Update Parameters:</string>
    <string name="request_failed">Request failed:</string>
    <string name="unknown_error">Unknown error</string>
    <string name="contrast">Contrast</string>
    <string name="hue">Hue</string>
    <string name="saturation">Saturation</string>
    <string name="lightness">Lightness</string>
    <string name="temperature">Temperature</string>
    <string name="highlight">Highlight</string>
    <string name="shadow">Shadow</string>
    <string name="sharpen">Sharpen</string>
    <string name="corner">Corner</string>
    <string name="no_significant_changes">No significant changes</string>
    
    <!-- AI Experiment pages -->
    <string name="experiment_home_description">This module uses OpenCV C++ algorithms and is currently only suitable for quick validation and parameter tuning of simple CV algorithms.</string>
    
    <!-- Experiment page navigation -->
    <string name="experiment_home">Home</string>
    <string name="experiment_binary_image">Binary Image</string>
    <string name="experiment_edge_detection">Edge Detection</string>
    <string name="experiment_contour_analysis">Contour Analysis</string>
    <string name="experiment_image_enhance">Image Enhancement</string>
    <string name="experiment_image_denoising">Image Denoising</string>
    <string name="experiment_morphological_operations">Morphological Operations</string>
    <string name="experiment_match_template">Template Matching</string>
    <string name="experiment_history">Parameter History</string>
    
    <!-- Binary image page -->
    <string name="please_binarize_image_first">Please binarize the current image first</string>
    <string name="please_select_canny_operator">Please select Canny operator</string>
    
    <!-- Contour analysis page -->
    <string name="contour_filter_settings">Contour Filter Settings</string>
    <string name="contour_display_settings">Contour Display Settings</string>
    <string name="perimeter">Perimeter</string>
    <string name="area">Area</string>
    <string name="roundness">Roundness</string>
    <string name="show_original_image">Show Original Image</string>
    <string name="show_bounding_rect">Show Bounding Rectangle</string>
    <string name="show_center">Show Center</string>
    <string name="perimeter_max_needs_double">Perimeter maximum needs double type</string>
    <string name="area_min_needs_double">Area minimum needs double type</string>
    <string name="area_max_needs_double">Area maximum needs double type</string>
    <string name="roundness_min_needs_double">Roundness minimum needs double type</string>
    <string name="roundness_max_needs_double">Roundness maximum needs double type</string>
    <string name="aspect_ratio_min_needs_double">Aspect ratio minimum needs double type</string>
    <string name="aspect_ratio_max_needs_double">Aspect ratio maximum needs double type</string>
    <string name="perimeter_at_least_one_value">Perimeter needs at least one minimum or maximum value</string>
    <string name="area_at_least_one_value">Area needs at least one minimum or maximum value</string>
    <string name="roundness_at_least_one_value">Roundness needs at least one minimum or maximum value</string>
    <string name="aspect_ratio_at_least_one_value">Aspect ratio needs at least one minimum or maximum value</string>
    <string name="please_binarize_image_first_for_contour">Please binarize the current image first</string>
    
    <string name="laplace_sharpen">Laplace Sharpen</string>
    <string name="usm_sharpen">USM Sharpen</string>
    <string name="auto_color_balance">Auto Color Balance</string>
    <string name="clip_limit_needs_double">clipLimit needs double type</string>
    <string name="size_needs_int_for_enhance">size needs int type</string>
    <string name="gamma_needs_float">gamma needs float type</string>
    <string name="radius_needs_int">Radius needs int type</string>
    <string name="threshold_needs_int">Threshold needs int type</string>
    <string name="amount_needs_int">Amount needs int type</string>
    <string name="ratio_needs_int">Ratio needs int type</string>
    
    <!-- Morphological operations page -->
    <string name="operating_elements">Operating Elements</string>
    <string name="structural_elements">Structural Elements</string>
    <string name="erosion">Erosion</string>
    <string name="dilation">Dilation</string>
    <string name="closing">Closing</string>
    <string name="morphological_gradient">Morphological Gradient</string>
    <string name="top_hat">Top Hat</string>
    <string name="black_hat">Black Hat</string>
    <string name="hit_miss">Hit Miss</string>
    <string name="cross">Cross</string>
    <string name="ellipse">Ellipse</string>
    <string name="width_needs_int_for_morph">width needs int type</string>
    <string name="height_needs_int_for_morph">height needs int type</string>
    
    <!-- Template matching page -->
    <string name="original_image_matching">Original Image Matching</string>
    <string name="grayscale_matching">Grayscale Matching</string>
    <string name="edge_matching">Edge Matching</string>
    <string name="import_template">Import Template:</string>
    <string name="delete_source_image">Delete source image</string>
    <string name="please_import_template_first">Please import template file first</string>
    <string name="angle_start_needs_int">angleStart needs int type, and angleStart &gt;= 0</string>
    <string name="angle_end_needs_int">angleEnd needs int type, and angleEnd &lt;= 360</string>
    <string name="angle_step_needs_int">angleStep needs int type, and angleStep &gt; 0</string>
    <string name="scale_start_needs_double">scaleStart needs double type, and scaleStart &gt;= 0</string>
    <string name="scale_end_needs_double">scaleEnd needs double type, and scaleStart &lt;= 1.0</string>
    <string name="scale_step_needs_double">scaleStep needs double type, and scaleStep &gt; 0</string>
    <string name="match_template_threshold_needs_double">matchTemplateThreshold needs double type, and matchTemplateThreshold &gt;= 0</string>
    <string name="score_threshold_needs_float">scoreThreshold needs float type, and scoreThreshold &gt;= 0</string>
    <string name="nms_threshold_needs_float">nmsThreshold needs float type, and nmsThreshold &gt;= 0</string>
    <string name="template_matching">Template Matching</string>
    
    <!-- History page -->
    <string name="operation">Operation</string>
    <string name="time">Time</string>
    <string name="parameters">Parameters</string>
    <string name="description">Description</string>
    
    <!-- Log messages -->
    <string name="threshold_type_cancelled">Threshold type cancelled</string>
    <string name="threshold_type_selected">Threshold type selected</string>
    <string name="global_threshold_cancelled">Global threshold segmentation cancelled</string>
    <string name="global_threshold_selected">Global threshold segmentation selected</string>
    <string name="adaptive_threshold_cancelled">Adaptive threshold segmentation cancelled</string>
    <string name="adaptive_threshold_selected">Adaptive threshold segmentation selected</string>
    <string name="opencv_debug_view_init">OpenCVDebugView initialization on startup</string>
    <string name="opencv_debug_view_dispose">OpenCVDebugView resource cleanup on close</string>
    
    <!-- Image enhancement page button texts -->
    <string name="histogram_equalization_button">Histogram Equalization</string>
    <string name="clahe_button">CLAHE</string>
    <string name="gamma_transform_button">Gamma Transform</string>
    <string name="laplace_sharpen_button">Laplace Sharpen</string>
    <string name="usm_sharpen_button">USM Sharpen</string>
    <string name="auto_color_balance_button">Auto Color Balance</string>
    
    <!-- Missing translations -->
    <string name="adaptive_threshold_algorithm">Adaptive Threshold Algorithm</string>
    <string name="adaptive_threshold_segmentation">Adaptive Threshold Segmentation</string>
    <string name="algorithm_service_error">Algorithm Service Error</string>
    <string name="global_threshold_segmentation">Global Threshold Segmentation</string>
    <string name="laplace_operator">Laplace Operator</string>
    <string name="opening">Opening</string>
    <string name="perimeter_min_needs_double">Perimeter minimum needs double type</string>
    <string name="please_select_image_first">Please select image first</string>
    <string name="roberts_operator">Roberts Operator</string>
    <string name="show_min_area_rect">Show Minimum Area Rectangle</string>
    
    <!-- Gemini API Settings -->
    <string name="gemini_api_key">Gemini API Key</string>
    <string name="gemini_api_key_title">Gemini API Key</string>
    <string name="gemini_api_key_description">Gemini API key for natural language color correction</string>
    <string name="ai_provider_selection">AI Service Provider</string>
    <string name="ai_provider_deepseek">DeepSeek</string>
    <string name="ai_provider_gemini">Gemini</string>
    <string name="select_ai_provider">Select AI Service Provider</string>
    
    <!-- Language Settings -->
    <string name="language_settings">Language Settings</string>
    <string name="current_language">Current Language</string>
    
    <!-- Doodle Module -->
    <string name="brush_settings">Brush Settings</string>
    <string name="clear_canvas">Clear Canvas</string>
    <string name="revoke">Revoke</string>
    <string name="stroke_cap">Stroke Cap</string>
    <string name="stroke_join">Stroke Join</string>
    
    <!-- Common button texts -->
    <string name="confirm">Confirm</string>
    <string name="cancel">Cancel</string>
    
    <!-- Version info -->
    <string name="pro_version">Pro Version</string>
    <string name="test_version">Test Version</string>
    
    <!-- Service status -->
    <string name="check_service_status">Check Service Status</string>
    <string name="algorithm_service_available">Algorithm Service Available</string>
    <string name="algorithm_service_unavailable">Algorithm Service Unavailable</string>
    
    <!-- Settings -->
    <string name="options_settings">Options Settings</string>
    
    <!-- Validation errors -->
    <string name="r_needs_int">R needs int type</string>
    <string name="g_needs_int">G needs int type</string>
    <string name="b_needs_int">B needs int type</string>
    <string name="size_needs_int">size needs int type</string>
    <string name="max_history_size_needs_int">maxHistorySizeText needs int type</string>
    <string name="please_enter_valid_url">Please enter a valid url</string>
    
    <!-- User prompts -->
    <string name="please_load_image_first">Please load an image first</string>
    
    <!-- Filter module related -->
    <string name="preview_effect">Preview Effect</string>
    <string name="previous_step">Previous Step</string>
    <string name="cancel_filter_operation">Cancel Filter Operation</string>
    <string name="save">Save</string>
    <string name="delete_original_image">Delete Original Image</string>
    <string name="remark">Remark</string>
    
    <!-- API Key prompt messages -->
    <string name="deepseek_api_key_missing">Please configure DeepSeek API Key in general settings first</string>
    <string name="gemini_api_key_missing">Please configure Gemini API Key in general settings first</string>
    <string name="options_settings">Options Settings</string>
    <string name="init_filter_params_config">Initialize Filter Parameters Config</string>
    <string name="clear_cache_data">Clear Cache Data</string>
    <string name="algorithm_service_url">Algorithm Service URL</string>
    <string name="enter_complete_algorithm_url">Please enter a complete algorithm service URL address</string>
    <string name="is_the_algorithm_service_available">Is the algorithm service available</string>
    <string name="algorithm_service_available">algorithm service available</string>
    <string name="algorithm_service_unavailable">algorithm service unavailable</string>
    <string name="theme_operations">Theme Operations</string>
    <string name="current_language">Current Language</string>
    <string name="chinese">Chinese</string>
    <string name="english">English</string>
    <string name="language_switch">Language Switch</string>
    <string name="language_operations">Language Operations</string>
    <string name="reset_to_chinese">Reset to Chinese</string>
    <string name="r_needs_int">R needs int type</string>
    <string name="g_needs_int">G needs int type</string>
    <string name="b_needs_int">B needs int type</string>
    <string name="size_needs_int">size needs int type</string>
    <string name="enter_valid_url">Please enter a valid url</string>
    <string name="max_history_size_needs_int">maxHistorySizeText needs int type</string>
    
    <!-- Window Titles -->
    <string name="window_title_doodle">Doodle Image</string>
    <string name="window_title_shape_drawing">Shape Drawing</string>
    <string name="window_title_color_pick">Color Picker</string>
    <string name="window_title_generate_gif">Generate GIF</string>
    <string name="window_title_crop_size">Image Crop</string>
    <string name="window_title_color_correction">Color Correction</string>
    <string name="window_title_filter">Apply Filter</string>
    <string name="window_title_opencv_debug">OpenCV Debug</string>
    <string name="window_title_face_swap">Face Swap</string>
    <string name="window_title_cartoon">Image Cartoonization</string>
    <string name="window_title_compression">Image Compression</string>
    <string name="window_title_web_screenshot">Web Long Screenshot</string>
    <string name="window_title_preview">Preview</string>
    
    <!-- Web Screenshot -->
    <string name="nodejs_not_installed">Node.js not detected</string>
    <string name="please_install_nodejs">Please install Node.js first (https://nodejs.org/)</string>
    <string name="website_url">Website URL</string>
    <string name="enter_url">Enter URL</string>
    <string name="screenshot_options">Screenshot Options</string>
    <string name="full_page_screenshot">Full Page Screenshot</string>
    <string name="wait_until">Wait Until</string>
    <string name="timeout_ms">Timeout (ms)</string>
    <string name="viewport_width">Viewport Width</string>
    <string name="viewport_height">Viewport Height</string>
    <string name="screenshot_clarity">Screenshot Clarity Scale</string>
    <string name="invalid_screenshot_clarity">Invalid Clarity Scale</string>
    <string name="please_enter_valid_screenshot_clarity">Enter a valid scale between 0 and 4, such as 1.5 or 2.0</string>
    <string name="capture_screenshot">Capture Screenshot</string>
    <string name="reset">Reset</string>
    <string name="usage_tips">Tip: Make sure Node.js and Playwright are installed. First-time use requires running 'npx playwright install chromium' to install the browser.</string>
    <string name="invalid_url">Invalid URL</string>
    <string name="please_enter_valid_url">Please enter a valid URL (starting with http:// or https://)</string>
</resources>


================================================
FILE: i18n/src/main/resources/strings/strings_zh.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <!-- 主窗口和菜单 -->
    <string name="app_name">Monica</string>
    <string name="app_description">Monica 是一个跨平台图像编辑器</string>
    <string name="software_version_info">软件版本信息</string>
    <string name="open_local_image">打开本地图片</string>
    <string name="load_network_image">加载网络图片</string>
    <string name="save_image">保存图像</string>
    <string name="screenshot_full_screen">截屏(全屏)</string>
    <string name="screenshot_area">截屏(区域选择)</string>
    <string name="web_screenshot">网页长截图</string>
    <string name="exit">退出</string>
    
    <!-- 控制面板 -->
    <string name="control_panel">控制面板</string>
    <string name="general_settings">通用设置</string>
    <string name="basic_functions">基础功能</string>
    <string name="color_correction">图像调色</string>
    <string name="filter_effects">滤镜效果</string>
    <string name="ai_laboratory">AI 实验室</string>
    
    <!-- 通用设置 -->
    <string name="settings">设置</string>
    <string name="monica_general_settings">Monica 通用设置</string>
    <string name="update">更新</string>
    <string name="close">关闭</string>
    <string name="cancel">取消</string>
    <string name="undo">撤销</string>
    <string name="undo_not_available">没有可撤销的操作</string>
    <string name="undo_success">已撤销</string>
    <string name="undo_and_reset_success">已撤销并重置</string>
    <string name="reset_done">已重置</string>
    
    <!-- 基础功能 -->
    <string name="image_blur">图像模糊</string>
    <string name="image_mosaic">图像马赛克</string>
    <string name="image_doodle">图像涂鸦</string>
    <string name="shape_drawing">图形绘制</string>
    <string name="color_picker">颜色选择器</string>
    <string name="crop_image">图像裁剪</string>
    <string name="generate_gif">生成 GIF</string>
    
    <!-- 图形绘制 -->
    <string name="select_color">选择颜色</string>
    <string name="change_properties">更改属性</string>
    <string name="line">线条</string>
    <string name="circle">圆形</string>
    <string name="triangle">三角形</string>
    <string name="rectangle">矩形</string>
    <string name="polygon">多边形</string>
    <string name="add_text">添加文字</string>
    <string name="save">保存</string>
    
    <!-- 涂鸦功能 -->
    <string name="brush">画笔</string>
    <string name="eraser">橡皮擦</string>
    <string name="clear">清除</string>
    <string name="brush_settings">画笔设置</string>
    <string name="clear_canvas">清空画布</string>
    <string name="revoke">撤回</string>
    
    <!-- 颜色校正 -->
    <string name="natural_language_color">自然语言调色</string>
    <string name="enter_color_instruction">请输入调色指令</string>
    <string name="color_parameters_updated">参数已更新:%s</string>
    <string name="natural_language_color_correction">自然语言图像调色</string>
    <string name="ai_provider_selection">AI 服务提供商</string>
    <string name="ai_provider_deepseek">DeepSeek</string>
    <string name="ai_provider_gemini">Gemini</string>
    <string name="contrast">对比度</string>
    <string name="hue">色调</string>
    <string name="saturation">饱和度</string>
    <string name="lightness">亮度</string>
    <string name="temperature">色温</string>
    <string name="api_key_required">需要配置 API Key</string>
    <string name="update_parameters">更新参数:</string>
    <string name="deepseek_api_key_missing">DeepSeek API Key 未配置</string>
    <string name="gemini_api_key_missing">Gemini API Key 未配置</string>
    <string name="request_failed">请求失败:</string>
    <string name="unknown_error">未知错误</string>
    <string name="highlight">高光</string>
    <string name="shadow">阴影</string>
    <string name="sharpen">锐化</string>
    <string name="corner">边角</string>
    <string name="no_significant_changes">无明显变化</string>
    
    <!-- 滤镜效果 -->
    <string name="filter_parameters">滤镜参数</string>
    
    <!-- AI 实验室 -->
    <string name="ai_laboratory_description">AI 实验室</string>
    <string name="simple_cv_algorithm">简单 CV 算法的快速验证</string>
    <string name="face_detection">人脸检测</string>
    <string name="generate_sketch">生成素描</string>
    <string name="face_swap">人脸替换</string>
    <string name="anime_style">动漫风格</string>
    
    <!-- AI 实验页面 -->
    <string name="home">首页</string>
    <string name="binary_image">二值图像</string>
    <string name="edge_detection">边缘检测</string>
    <string name="contour_analysis">轮廓分析</string>
    <string name="image_enhance">图像增强</string>
    <string name="image_denoising">图像去噪</string>
    <string name="morphological_operations">形态学操作</string>
    <string name="match_template">模板匹配</string>
    <string name="parameter_history">参数历史</string>
    
    <!-- 裁剪功能 -->
    <string name="crop_type">裁剪类型</string>
    <string name="content_scale">内容缩放</string>
    <string name="aspect_ratio">宽高比</string>
    <string name="crop_frame">裁剪框</string>
    <string name="crop_properties_settings">裁剪属性设置</string>
    <string name="confirm_crop">确认</string>
    <string name="dismiss_crop">取消</string>
    
    <!-- 对话框和提示 -->
    <string name="loading">加载中...</string>
    <string name="error">错误</string>
    <string name="success">成功</string>
    <string name="warning">警告</string>
    <string name="info">信息</string>
    
    <!-- 属性设置 -->
    <string name="alpha">透明度</string>
    <string name="font_size">字体大小</string>
    <string name="fill">填充</string>
    <string name="border">边框</string>
    <string name="stroke_width">描边宽度</string>
    <string name="stroke_cap">描边端点</string>
    <string name="stroke_join">描边连接</string>
    <string name="confirm">确认</string>
    
    <!-- 版本信息 -->
    <string name="pro_version">正式版本</string>
    <string name="test_version">测试版本</string>
    
    <!-- 服务状态 -->
    <string name="check_service_status">检测服务状态</string>
    <string name="algorithm_service_available">算法服务可用</string>
    <string name="algorithm_service_unavailable">算法服务不可用</string>
    
    <!-- 设置 -->
    <string name="options_settings">选项设置</string>
    
    <!-- 验证错误 -->
    <string name="r_needs_int">R 需要 int 类型</string>
    <string name="g_needs_int">G 需要 int 类型</string>
    <string name="b_needs_int">B 需要 int 类型</string>
    <string name="size_needs_int">size 需要 int 类型</string>
    <string name="max_history_size_needs_int">maxHistorySizeText 需要 int 类型</string>
    <string name="please_enter_valid_url">请输入一个正确的 url</string>
    
    <!-- 用户提示 -->
    <string name="please_load_image_first">请先加载图片</string>
    
    <!-- 滤镜模块相关 -->
    <string name="preview_effect">预览效果</string>
    <string name="previous_step">上一步</string>
    <string name="cancel_filter_operation">取消滤镜操作</string>
    <string name="save">保存</string>
    <string name="delete_original_image">删除原图</string>
    <string name="remark">备注</string>
    
    <!-- 语言设置 -->
    <string name="language_settings">语言设置</string>
    <string name="current_language">当前语言</string>
    
    <!-- 网络图片加载 -->
    <string name="load_network_image_dialog">加载网络图片</string>

    <!-- 主题设置 -->
    <string name="basic_settings">基础设置</string>
    <string name="api_settings">API设置</string>
    <string name="theme_settings">主题设置</string>
    <string name="language_settings">语言设置</string>
    <string name="current_theme">当前主题</string>
    <string name="select_theme">选择主题</string>
    <string name="theme_light">浅色主题</string>
    <string name="theme_dark">深色主题</string>
    <string name="theme_blue">蓝色主题</string>
    <string name="theme_green">绿色主题</string>
    <string name="theme_purple">紫色主题</string>
    <string name="theme_orange">橙色主题</string>
    <string name="theme_pink">粉色主题</string>
    <string name="reset_to_default_theme">重置为默认主题</string>
    <string name="enter_image_url">请输入图片URL</string>
    <string name="load">加载</string>
    <string name="url_invalid">无效的URL格式</string>
    
    <!-- 版本信息 -->
    <string name="version_info">版本信息</string>
    <string name="copyright">© 2024 Tony Shen. 保留所有权利。</string>
    
    <!-- 状态和消息 -->
    <string name="basic_function_cancelled">基础功能已取消</string>
    <string name="basic_function_selected">基础功能已选择</string>
    <string name="general_settings_cancelled">通用设置已取消</string>
    <string name="general_settings_selected">通用设置已选择</string>
    <string name="color_correction_cancelled">图像调色已取消</string>
    <string name="color_correction_selected">图像调色已选择</string>
    <string name="filter_cancelled">滤镜效果已取消</string>
    <string name="filter_selected">滤镜效果已选择</string>
    <string name="ai_laboratory_cancelled">AI 实验室已取消</string>
    <string name="ai_laboratory_selected">AI 实验室已选择</string>
    
    <!-- 工具提示 -->
    <string name="simple_cv_tooltip">简单 CV 算法的快速验证</string>
    <string name="face_detect_tooltip">人脸检测</string>
    <string name="sketch_drawing_tooltip">生成素描</string>
    <string name="face_swa
Download .txt
gitextract_0i8k_7pw/

├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── FUNCTION.md
├── LICENSE
├── README-EN.md
├── README.md
├── build.gradle.kts
├── compose-desktop.pro
├── config/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── config/
│                               ├── Constants.kt
│                               ├── SystemConstants.kt
│                               ├── category/
│                               │   ├── ConfigCategory.kt
│                               │   ├── ConfigCategoryManager.kt
│                               │   ├── ConfigDefinitions.kt
│                               │   └── ConfigValidator.kt
│                               └── storage/
│                                   ├── ConfigManager.kt
│                                   ├── ConfigStorage.kt
│                                   ├── FileConfigStorage.kt
│                                   ├── PreferencesConfigStorage.kt
│                                   └── RxCacheConfigStorage.kt
├── docs/
│   ├── filter_module_refactor.md
│   ├── layer_render_cache_analysis.md
│   ├── layer_system.md
│   └── layer_system_optimization_roadmap.md
├── domain/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── domain/
│                               ├── ColorCorrectionSettings.kt
│                               ├── ContourDisplaySettings.kt
│                               ├── ContourFilterSettings.kt
│                               ├── DecodedPreviewImage.kt
│                               ├── GeneralSettings.kt
│                               ├── MatchTemplateSettings.kt
│                               ├── MorphologicalOperationSettings.kt
│                               └── NativeImage.kt
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── i18n/
│   ├── QUICK_REFERENCE.md
│   ├── README.md
│   ├── SCRIPT_USAGE_GUIDE.md
│   ├── build.gradle.kts
│   ├── position_check.sh
│   ├── quick_check.sh
│   ├── src/
│   │   ├── main/
│   │   │   ├── kotlin/
│   │   │   │   └── cn/
│   │   │   │       └── netdiscovery/
│   │   │   │           └── monica/
│   │   │   │               └── i18n/
│   │   │   │                   ├── Language.kt
│   │   │   │                   ├── LocalizationManager.kt
│   │   │   │                   └── XmlStringResource.kt
│   │   │   └── resources/
│   │   │       └── strings/
│   │   │           ├── strings_en.xml
│   │   │           └── strings_zh.xml
│   │   └── test/
│   │       └── kotlin/
│   │           └── cn/
│   │               └── netdiscovery/
│   │                   └── monica/
│   │                       └── i18n/
│   │                           └── InternationalizationTest.kt
│   └── string_manager.sh
├── imageprocess/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── imageprocess/
│                               ├── BufferedImages.kt
│                               ├── Colormap.kt
│                               ├── IntIntegralImage.kt
│                               ├── Transformer.kt
│                               ├── domain/
│                               │   ├── ArrayColormap.kt
│                               │   ├── Gradient.kt
│                               │   └── Histogram.kt
│                               ├── filter/
│                               │   ├── BilateralFilter.kt
│                               │   ├── BlockFilter.kt
│                               │   ├── BumpFilter.kt
│                               │   ├── CarveFilter.kt
│                               │   ├── CellularFilter.kt
│                               │   ├── ColorFilter.kt
│                               │   ├── ColorHalftoneFilter.kt
│                               │   ├── ConBriFilter.kt
│                               │   ├── CropFilter.kt
│                               │   ├── CrystallizeFilter.kt
│                               │   ├── DiffuseFilter.kt
│                               │   ├── EmbossFilter.kt
│                               │   ├── EqualizeFilter.kt
│                               │   ├── ExposureFilter.kt
│                               │   ├── GainFilter.kt
│                               │   ├── GammaFilter.kt
│                               │   ├── GaussianNoiseFilter.kt
│                               │   ├── GradientFilter.kt
│                               │   ├── GrayFilter.kt
│                               │   ├── HSBAdjustFilter.kt
│                               │   ├── HighPassFilter.kt
│                               │   ├── InvertFilter.kt
│                               │   ├── MarbleFilter.kt
│                               │   ├── MirrorFilter.kt
│                               │   ├── MosaicFilter.kt
│                               │   ├── NatureFilter.kt
│                               │   ├── OffsetFilter.kt
│                               │   ├── OilPaintFilter.kt
│                               │   ├── PointillizeFilter.kt
│                               │   ├── PosterizeFilter.kt
│                               │   ├── RippleFilter.kt
│                               │   ├── SepiaToneFilter.kt
│                               │   ├── SmearFilter.kt
│                               │   ├── SolarizeFilter.kt
│                               │   ├── SpotlightFilter.kt
│                               │   ├── StrokeAreaFilter.kt
│                               │   ├── SwimFilter.kt
│                               │   ├── VignetteFilter.kt
│                               │   ├── WaterFilter.kt
│                               │   ├── WhiteImageFilter.kt
│                               │   ├── base/
│                               │   │   ├── BaseFilter.kt
│                               │   │   ├── ColorProcessorFilter.kt
│                               │   │   ├── ConvolveFilter.kt
│                               │   │   ├── PointFilter.kt
│                               │   │   ├── TransferFilter.kt
│                               │   │   ├── TransformFilter.kt
│                               │   │   └── WholeImageFilter.kt
│                               │   ├── blur/
│                               │   │   ├── AverageFilter.kt
│                               │   │   ├── BoxBlurFilter.kt
│                               │   │   ├── FastBlur2D.kt
│                               │   │   ├── GaussianFilter.kt
│                               │   │   ├── LensBlurFilter.kt
│                               │   │   ├── MaximumFilter.kt
│                               │   │   ├── MinimumFilter.kt
│                               │   │   ├── MotionFilter.kt
│                               │   │   └── VariableBlurFilter.kt
│                               │   └── sharpen/
│                               │       ├── LaplaceSharpenFilter.kt
│                               │       ├── SharpenFilter.kt
│                               │       └── USMFilter.kt
│                               ├── lut/
│                               │   ├── AutumnLUT.kt
│                               │   ├── BoneLUT.kt
│                               │   ├── CoolLUT.kt
│                               │   ├── HotLUT.kt
│                               │   ├── HsvLUT.kt
│                               │   ├── JetLUT.kt
│                               │   ├── LUT.kt
│                               │   ├── OceanLUT.kt
│                               │   ├── PinkLUT.kt
│                               │   ├── RainbowLUT.kt
│                               │   ├── SpringLUT.kt
│                               │   ├── SummerLUT.kt
│                               │   └── WinterLUT.kt
│                               ├── math/
│                               │   ├── FFT.kt
│                               │   ├── Functions.kt
│                               │   ├── ImageMath.kt
│                               │   └── Noise.kt
│                               └── utils/
│                                   ├── ImageUtils.kt
│                                   └── extension/
│                                       └── BufferedImage+Extensions.kt
├── opencv/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── cn/
│                   └── netdiscovery/
│                       └── monica/
│                           └── opencv/
│                               └── ImageProcess.kt
├── resources/
│   ├── common/
│   │   └── filterConfig.json
│   ├── package.json
│   └── web-screenshot.js
├── settings.gradle.kts
└── src/
    ├── jvmMain/
    │   ├── kotlin/
    │   │   ├── Main.kt
    │   │   └── cn/
    │   │       └── netdiscovery/
    │   │           └── monica/
    │   │               ├── config/
    │   │               │   └── Constant.kt
    │   │               ├── di/
    │   │               │   └── appModule.kt
    │   │               ├── exception/
    │   │               │   ├── AppError.kt
    │   │               │   ├── ErrorComposable.kt
    │   │               │   ├── ErrorExtensions.kt
    │   │               │   ├── ErrorHandler.kt
    │   │               │   ├── ErrorManager.kt
    │   │               │   ├── ErrorState.kt
    │   │               │   ├── Errors.kt
    │   │               │   ├── MonicaException.kt
    │   │               │   └── handlers/
    │   │               │       ├── AIServiceErrorHandler.kt
    │   │               │       ├── FileIOErrorHandler.kt
    │   │               │       ├── ImageProcessingErrorHandler.kt
    │   │               │       ├── NetworkErrorHandler.kt
    │   │               │       └── ValidationErrorHandler.kt
    │   │               ├── history/
    │   │               │   ├── EditHistoryCenter.kt
    │   │               │   ├── EditHistoryManager.kt
    │   │               │   ├── HistoryEntry.kt
    │   │               │   └── modules/
    │   │               │       ├── colorcorrection/
    │   │               │       │   └── ColorCorrectionParams.kt
    │   │               │       └── opencv/
    │   │               │           └── CVParams.kt
    │   │               ├── http/
    │   │               │   ├── GsonSerializer.kt
    │   │               │   └── HttpClient.kt
    │   │               ├── llm/
    │   │               │   ├── DeepSeekRequest.kt
    │   │               │   ├── DeepseekClient.kt
    │   │               │   ├── DialogSession.kt
    │   │               │   ├── GeminiClient.kt
    │   │               │   ├── GeminiRequest.kt
    │   │               │   └── LLMServiceManager.kt
    │   │               ├── manager/
    │   │               │   └── OpenCVManager.kt
    │   │               ├── state/
    │   │               │   └── ApplicationState.kt
    │   │               ├── ui/
    │   │               │   ├── controlpanel/
    │   │               │   │   ├── BasicView.kt
    │   │               │   │   ├── ai/
    │   │               │   │   │   ├── AIView.kt
    │   │               │   │   │   ├── AIViewModel.kt
    │   │               │   │   │   ├── experiment/
    │   │               │   │   │   │   ├── BinaryImageView.kt
    │   │               │   │   │   │   ├── CVState.kt
    │   │               │   │   │   │   ├── ContourAnalysisView.kt
    │   │               │   │   │   │   ├── EdgeDetectionView.kt
    │   │               │   │   │   │   ├── ExperimentHome.kt
    │   │               │   │   │   │   ├── ExperimentView.kt
    │   │               │   │   │   │   ├── HistoryView.kt
    │   │               │   │   │   │   ├── ImageDenoisingView.kt
    │   │               │   │   │   │   ├── ImageEnhanceView.kt
    │   │               │   │   │   │   ├── MatchTemplateView.kt
    │   │               │   │   │   │   ├── MorphologicalOperationsView.kt
    │   │               │   │   │   │   ├── NavController.kt
    │   │               │   │   │   │   ├── NavigationHost.kt
    │   │               │   │   │   │   └── viewmodel/
    │   │               │   │   │   │       ├── BinaryImageViewModel.kt
    │   │               │   │   │   │       ├── ContourAnalysisViewModel.kt
    │   │               │   │   │   │       ├── EdgeDetectionViewModel.kt
    │   │               │   │   │   │       ├── HistoryViewModel.kt
    │   │               │   │   │   │       ├── ImageDenoisingViewModel.kt
    │   │               │   │   │   │       ├── ImageEnhanceViewModel.kt
    │   │               │   │   │   │       ├── MatchTemplateViewModel.kt
    │   │               │   │   │   │       └── MorphologicalOperationsViewModel.kt
    │   │               │   │   │   └── faceswap/
    │   │               │   │   │       ├── FaceSwapView.kt
    │   │               │   │   │       └── FaceSwapViewModel.kt
    │   │               │   │   ├── cartoon/
    │   │               │   │   │   ├── CartoonView.kt
    │   │               │   │   │   └── CartoonViewModel.kt
    │   │               │   │   ├── colorcorrection/
    │   │               │   │   │   ├── ColorCorrectionView.kt
    │   │               │   │   │   ├── ColorCorrectionViewModel.kt
    │   │               │   │   │   └── NaturalLanguageDialog.kt
    │   │               │   │   ├── colorpick/
    │   │               │   │   │   ├── ColorPickView.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   ├── ColorData.kt
    │   │               │   │   │   │   ├── ColorNameMap.kt
    │   │               │   │   │   │   └── ColorNameParser.kt
    │   │               │   │   │   ├── utils/
    │   │               │   │   │   │   ├── ColorDetection.kt
    │   │               │   │   │   │   ├── ColorUtils.kt
    │   │               │   │   │   │   └── RoundngUtils.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       ├── ColorDisplay.kt
    │   │               │   │   │       ├── ColorSelectionDrawing.kt
    │   │               │   │   │       └── ImageColorDetector.kt
    │   │               │   │   ├── compression/
    │   │               │   │   │   ├── CompressionActions.kt
    │   │               │   │   │   ├── CompressionAlgorithmDropdown.kt
    │   │               │   │   │   ├── CompressionInputSection.kt
    │   │               │   │   │   ├── CompressionPreview.kt
    │   │               │   │   │   ├── CompressionProgress.kt
    │   │               │   │   │   ├── CompressionSliders.kt
    │   │               │   │   │   ├── CompressionView.kt
    │   │               │   │   │   └── CompressionViewModel.kt
    │   │               │   │   ├── cropimage/
    │   │               │   │   │   ├── CropAgent.kt
    │   │               │   │   │   ├── CropImageSettingView.kt
    │   │               │   │   │   ├── CropImageView.kt
    │   │               │   │   │   ├── CropModifier.kt
    │   │               │   │   │   ├── CropViewModel.kt
    │   │               │   │   │   ├── ImageCropper.kt
    │   │               │   │   │   ├── TouchRegion.kt
    │   │               │   │   │   ├── draw/
    │   │               │   │   │   │   ├── ImageDrawCanvas.kt
    │   │               │   │   │   │   └── Overlay.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   ├── CropAspectRatio.kt
    │   │               │   │   │   │   ├── CropFrame.kt
    │   │               │   │   │   │   ├── CropOutline.kt
    │   │               │   │   │   │   ├── CropOutlineContainer.kt
    │   │               │   │   │   │   └── CropOutlineProperties.kt
    │   │               │   │   │   ├── setting/
    │   │               │   │   │   │   ├── CropDefaults.kt
    │   │               │   │   │   │   ├── CropFrameFactory.kt
    │   │               │   │   │   │   └── Paths.kt
    │   │               │   │   │   ├── state/
    │   │               │   │   │   │   ├── CropState.kt
    │   │               │   │   │   │   ├── CropStateImpl.kt
    │   │               │   │   │   │   ├── DynamicCropState.kt
    │   │               │   │   │   │   ├── StaticCropState.kt
    │   │               │   │   │   │   └── TransformState.kt
    │   │               │   │   │   └── utils/
    │   │               │   │   │       ├── DrawScopeUtils.kt
    │   │               │   │   │       ├── ShapeUtils.kt
    │   │               │   │   │       └── ZoomUtils.kt
    │   │               │   │   ├── doodle/
    │   │               │   │   │   ├── DoodleView.kt
    │   │               │   │   │   ├── DoodleViewModel.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   └── PathProperties.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       └── PropertiesMenuDialog.kt
    │   │               │   │   ├── filter/
    │   │               │   │   │   ├── FilterView.kt
    │   │               │   │   │   ├── viewmodel/
    │   │               │   │   │   │   └── FilterViewModel.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       ├── FilterAdjustmentPanel.kt
    │   │               │   │   │       ├── FilterListPanel.kt
    │   │               │   │   │       ├── FilterParamDefaults.kt
    │   │               │   │   │       ├── FilterParamMeta.kt
    │   │               │   │   │       ├── FilterPreviewArea.kt
    │   │               │   │   │       └── FilterTopAppBar.kt
    │   │               │   │   ├── generategif/
    │   │               │   │   │   ├── GenerateGifView.kt
    │   │               │   │   │   └── GenerateGifViewModel.kt
    │   │               │   │   ├── shapedrawing/
    │   │               │   │   │   ├── CoordinateSystem.kt
    │   │               │   │   │   ├── EditorController.kt
    │   │               │   │   │   ├── ShapeDrawingView.kt
    │   │               │   │   │   ├── ShapeDrawingViewModel.kt
    │   │               │   │   │   ├── animation/
    │   │               │   │   │   │   └── ShapeAnimationManager.kt
    │   │               │   │   │   ├── coordinate/
    │   │               │   │   │   │   └── CoordinateConverter.kt
    │   │               │   │   │   ├── geometry/
    │   │               │   │   │   │   ├── CanvasDrawer.kt
    │   │               │   │   │   │   ├── Drawer.kt
    │   │               │   │   │   │   └── Style.kt
    │   │               │   │   │   ├── handler/
    │   │               │   │   │   │   └── ShapeDrawingEventHandler.kt
    │   │               │   │   │   ├── helper/
    │   │               │   │   │   │   └── SpecialLayerHelper.kt
    │   │               │   │   │   ├── layer/
    │   │               │   │   │   │   ├── ImageLayer.kt
    │   │               │   │   │   │   ├── Layer.kt
    │   │               │   │   │   │   ├── LayerManager.kt
    │   │               │   │   │   │   ├── LayerRenderer.kt
    │   │               │   │   │   │   ├── ShapeLayer.kt
    │   │               │   │   │   │   └── SpecialLayerHelper.kt
    │   │               │   │   │   ├── model/
    │   │               │   │   │   │   ├── Shape.kt
    │   │               │   │   │   │   └── ShapeProperties.kt
    │   │               │   │   │   ├── state/
    │   │               │   │   │   │   └── ShapeDrawingState.kt
    │   │               │   │   │   └── widget/
    │   │               │   │   │       ├── CanvasView.kt
    │   │               │   │   │       ├── DraggableTextField.kt
    │   │               │   │   │       ├── ImageLayerControlRenderer.kt
    │   │               │   │   │       ├── LayerPanel.kt
    │   │               │   │   │       ├── ShapeDrawingPropertiesMenuDialog.kt
    │   │               │   │   │       └── TextDrawer.kt
    │   │               │   │   └── webscreenshot/
    │   │               │   │       ├── WebScreenshotView.kt
    │   │               │   │       └── WebScreenshotViewModel.kt
    │   │               │   ├── i18n/
    │   │               │   │   └── ComposeI18n.kt
    │   │               │   ├── main/
    │   │               │   │   ├── ContentPanel.kt
    │   │               │   │   ├── Dialogs.kt
    │   │               │   │   ├── GeneralSettingsDialog.kt
    │   │               │   │   ├── MainView.kt
    │   │               │   │   ├── MainViewModel.kt
    │   │               │   │   └── SidebarView.kt
    │   │               │   ├── preview/
    │   │               │   │   ├── PreviewViewModel.kt
    │   │               │   │   └── PreviewViewt.kt
    │   │               │   ├── screenshot/
    │   │               │   │   └── SwingScreenshotAreaSelector.kt
    │   │               │   ├── showimage/
    │   │               │   │   └── ShowImageView.kt
    │   │               │   ├── theme/
    │   │               │   │   ├── ColorTheme.kt
    │   │               │   │   └── ThemeManager.kt
    │   │               │   └── widget/
    │   │               │       ├── Buttons.kt
    │   │               │       ├── Checkboxs.kt
    │   │               │       ├── Divider.kt
    │   │               │       ├── LazyRow.kt
    │   │               │       ├── PageLifecycle.kt
    │   │               │       ├── RightSideMenuBar.kt
    │   │               │       ├── TextFields.kt
    │   │               │       ├── ThreeBallLoading.kt
    │   │               │       ├── Title.kt
    │   │               │       ├── Toasts.kt
    │   │               │       ├── color/
    │   │               │       │   ├── ColorSelection.kt
    │   │               │       │   └── ColorSelectionDialog.kt
    │   │               │       ├── image/
    │   │               │       │   ├── ImageContentScaleUtil.kt
    │   │               │       │   ├── ImageScope.kt
    │   │               │       │   ├── ImageSizeCalculator.kt
    │   │               │       │   ├── ImageWithConstraints.kt
    │   │               │       │   ├── ImageWithThumbnail.kt
    │   │               │       │   └── gesture/
    │   │               │       │       ├── AwaitDragMotionModifier.kt
    │   │               │       │       ├── AwaitPointerMontionEvent.kt
    │   │               │       │       ├── MotionEvent.kt
    │   │               │       │       ├── PointerMotionModify.kt
    │   │               │       │       └── TransformGestures.kt
    │   │               │       └── properties/
    │   │               │           └── ExposedSelectionMenu.kt
    │   │               └── utils/
    │   │                   ├── AppDirs.kt
    │   │                   ├── ButtonUtils.kt
    │   │                   ├── DebugUtils.kt
    │   │                   ├── FileChoose.kt
    │   │                   ├── IOUtils.kt
    │   │                   ├── ImageCompressionUtils.kt
    │   │                   ├── ImageFormatDetector.kt
    │   │                   ├── ImageUtils.kt
    │   │                   ├── LogHomeProperty.kt
    │   │                   ├── LogUtils.kt
    │   │                   ├── ScreenshotUtils.kt
    │   │                   ├── TextUtils.kt
    │   │                   ├── TimeUtils.kt
    │   │                   ├── Typealiases.kt
    │   │                   ├── Validation.kt
    │   │                   ├── WebScreenshotUtils.kt
    │   │                   └── extensions/
    │   │                       ├── Any+Extensions.kt
    │   │                       ├── Coroutine+Extensions.kt
    │   │                       ├── DrawScope+Extensions.kt
    │   │                       ├── Number+Extensions.kt
    │   │                       └── String+Extensions.kt
    │   └── resources/
    │       └── logback.xml
    └── jvmTest/
        └── kotlin/
            └── cn/
                └── netdiscovery/
                    └── monica/
                        └── editor/
                            └── layer/
                                ├── ExportManagerTest.kt
                                └── LayerManagerTest.kt
Download .txt
SYMBOL INDEX (5 symbols across 1 files)

FILE: resources/web-screenshot.js
  function autoScrollPage (line 44) | async function autoScrollPage(page, timeout) {
  function forceLazyImages (line 84) | async function forceLazyImages(page) {
  function waitForImages (line 115) | async function waitForImages(page, timeout) {
  function settleLongPage (line 148) | async function settleLongPage(page, timeout) {
  function captureScreenshot (line 155) | async function captureScreenshot() {
Condensed preview — 351 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,930K chars).
[
  {
    "path": ".gitattributes",
    "chars": 66,
    "preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".gitignore",
    "chars": 382,
    "preview": ".gradle\nbuild/\n!gradle/wrapper/gradle-wrapper.jar\n!**/src/main/**/build/\n!**/src/test/**/build/\nbin/\nconfig/bin/\ndomain/"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2058,
    "preview": "Monica\n===\n\nVersion 1.1.4\n---\n2025-9-25\n* 增加使用 Gemini 实现自然语言实现调色的功能\n* 增加主题的功能,Monica 可以切换主题\n* 增加国际化,支持英文版本\n* 重构 Monica U"
  },
  {
    "path": "FUNCTION.md",
    "chars": 2279,
    "preview": "## 2.1 基础功能\n加载完图像后,就可以对图像进行各种编辑和操作\n![](images/1-1.png)\n\nMonica 基础功能的按钮,都带有 tooltips ,例如这个涂鸦功能\n![](images/1-2.png)\n\n点击按钮就"
  },
  {
    "path": "LICENSE",
    "chars": 11324,
    "preview": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licens"
  },
  {
    "path": "README-EN.md",
    "chars": 4888,
    "preview": "**Monica** is a cross-platform desktop image editor.\nIt supports a wide range of image formats (including camera RAW), i"
  },
  {
    "path": "README.md",
    "chars": 3625,
    "preview": "**Monica** 是一款跨平台的桌面图像编辑软件。它不仅支持多种图像格式(包括相机 RAW),还集成了传统图像处理和基于深度学习的图像增强功能,提供可扩展、可二次开发的图像编辑体验。\n\n# 🧪 技术栈\n* **UI 框架**:Kotli"
  },
  {
    "path": "build.gradle.kts",
    "chars": 21522,
    "preview": "import org.jetbrains.compose.desktop.application.dsl.TargetFormat\nimport java.io.File\nimport java.io.FileOutputStream\nim"
  },
  {
    "path": "compose-desktop.pro",
    "chars": 275,
    "preview": "-dontwarn org.slf4j.**\n-dontwarn ch.qos.logback.**\n-dontwarn com.google.gson.**\n-dontwarn org.apache.**\n-dontwarn com.sa"
  },
  {
    "path": "config/build.gradle.kts",
    "chars": 2114,
    "preview": " plugins {\n    kotlin(\"jvm\")\n    id(\"com.github.gmazzo.buildconfig\") version \"5.4.0\"\n}\n\nrepositories {\n    mavenCentral("
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/Constants.kt",
    "chars": 1040,
    "preview": "package cn.netdiscovery.monica.config\n\nimport java.text.SimpleDateFormat\n\n\n/**\n *\n * @FileName:\n *          cn.netdiscov"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/SystemConstants.kt",
    "chars": 750,
    "preview": "package cn.netdiscovery.monica.config\n\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.utils.SystemUtils\n * @au"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigCategory.kt",
    "chars": 530,
    "preview": "package cn.netdiscovery.monica.config.category\n\n/**\n * 配置分类枚举\n * \n * 用于区分不同类型的配置,便于统一管理和验证。\n * \n * @author: Tony Shen\n *"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigCategoryManager.kt",
    "chars": 4502,
    "preview": "package cn.netdiscovery.monica.config.category\n\nimport cn.netdiscovery.monica.config.storage.ConfigManager\nimport cn.net"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigDefinitions.kt",
    "chars": 1857,
    "preview": "package cn.netdiscovery.monica.config.category\n\nimport cn.netdiscovery.monica.config.KEY_GENERAL_SETTINGS\nimport cn.netd"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/category/ConfigValidator.kt",
    "chars": 3557,
    "preview": "package cn.netdiscovery.monica.config.category\n\nimport org.slf4j.Logger\nimport org.slf4j.LoggerFactory\n\n/**\n * 配置验证器接口\n "
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/storage/ConfigManager.kt",
    "chars": 3910,
    "preview": "package cn.netdiscovery.monica.config.storage\n\nimport com.safframework.rxcache.RxCache\nimport org.slf4j.Logger\nimport or"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/storage/ConfigStorage.kt",
    "chars": 877,
    "preview": "package cn.netdiscovery.monica.config.storage\n\n/**\n * 统一配置存储接口\n * \n * 抽象了不同存储实现的差异,提供统一的配置读写接口。\n * \n * @author: Tony She"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/storage/FileConfigStorage.kt",
    "chars": 4536,
    "preview": "package cn.netdiscovery.monica.config.storage\n\nimport com.google.gson.Gson\nimport com.google.gson.reflect.TypeToken\nimpo"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/storage/PreferencesConfigStorage.kt",
    "chars": 4047,
    "preview": "package cn.netdiscovery.monica.config.storage\n\nimport org.slf4j.Logger\nimport org.slf4j.LoggerFactory\nimport java.util.p"
  },
  {
    "path": "config/src/main/kotlin/cn/netdiscovery/monica/config/storage/RxCacheConfigStorage.kt",
    "chars": 4407,
    "preview": "package cn.netdiscovery.monica.config.storage\n\nimport com.google.gson.Gson\nimport com.google.gson.reflect.TypeToken\nimpo"
  },
  {
    "path": "docs/filter_module_refactor.md",
    "chars": 5043,
    "preview": "# 滤镜模块 UI 重构与优化说明(2025-12)\n\n## 背景与目标\n\n本次重构的核心目标:\n\n- **提升 UI 可用性与一致性**:对齐、间距、状态提示更清晰,符合图像编辑软件的交互预期。\n- **保持业务逻辑不变/可控演进**:在"
  },
  {
    "path": "docs/layer_render_cache_analysis.md",
    "chars": 3603,
    "preview": "# 图层渲染缓存优化分析\n\n## 当前实现分析\n\n### 1. 渲染流程\n- `CanvasView` 使用 `collectAsState()` 观察图层列表\n- 每次图层变化都会触发 Canvas 重组和重绘\n- `LayerRende"
  },
  {
    "path": "docs/layer_system.md",
    "chars": 4851,
    "preview": "# 图层系统概览\n\n本文档记录 Monica 图层系统的最新实现,用于指导后续的功能扩展与维护。\n\n## 核心目标\n\n- 支持图像层与形状层的叠加管理,便于多图层编辑。\n- 统一渲染与导出流程,避免重复绘制逻辑。\n- 提供直观的 UI 面板"
  },
  {
    "path": "docs/layer_system_optimization_roadmap.md",
    "chars": 14326,
    "preview": "# 图层系统优化路线图\n\n本文档记录 Monica 图层系统的优化方向和实施计划,用于指导后续的功能扩展与性能提升。\n\n**文档版本**: 1.0  \n**最后更新**: 2024-12  \n**维护者**: Monica 开发团队\n\n--"
  },
  {
    "path": "domain/build.gradle.kts",
    "chars": 252,
    "preview": "plugins {\n    kotlin(\"jvm\")\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation(kotlin(\"test\")"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/ColorCorrectionSettings.kt",
    "chars": 800,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.domain.ColorCorrectionSet"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/ContourDisplaySettings.kt",
    "chars": 437,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.ui.controlpanel.ai.experi"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/ContourFilterSettings.kt",
    "chars": 547,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.ui.controlpanel.ai.experi"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/DecodedPreviewImage.kt",
    "chars": 1080,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.domain.DecodedPreviewImag"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/GeneralSettings.kt",
    "chars": 476,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.domain.GeneralSettings\n *"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/MatchTemplateSettings.kt",
    "chars": 710,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.ui.controlpanel.ai.experi"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/MorphologicalOperationSettings.kt",
    "chars": 377,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.ui.controlpanel.ai.experi"
  },
  {
    "path": "domain/src/main/kotlin/cn/netdiscovery/monica/domain/NativeImage.kt",
    "chars": 836,
    "preview": "package cn.netdiscovery.monica.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.domain.NativeImage\n * @au"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 199,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "gradle.properties",
    "chars": 270,
    "preview": "kotlin.code.style=official\napp.version=1.1.5\nkotlin.version=2.1.0\nagp.version=7.3.0\ncompose.version=1.6.11\nkotlinx.corou"
  },
  {
    "path": "gradlew",
    "chars": 8047,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "gradlew.bat",
    "chars": 2674,
    "preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "i18n/QUICK_REFERENCE.md",
    "chars": 1258,
    "preview": "# i18n 脚本快速参考\n\n## 🚀 快速开始\n\n### 基本检查\n```bash\n# 检查中英文文件是否同步\n./string_manager.sh -m\n\n# 检查重复项(不修改文件)\n./string_manager.sh -c -"
  },
  {
    "path": "i18n/README.md",
    "chars": 1260,
    "preview": "# 🌍 Monica 国际化工具集\n\n## 📋 概述\n\nMonica 项目的国际化字符串资源管理工具集,提供完整的字符串资源文件管理解决方案。\n\n## 🛠️ 工具列表\n\n### 核心工具\n- **`string_manager.sh`** "
  },
  {
    "path": "i18n/SCRIPT_USAGE_GUIDE.md",
    "chars": 4359,
    "preview": "# i18n 脚本使用指南\n\n## 📖 概述\n\n本指南介绍 i18n 模块中两个字符串资源管理脚本的使用方法:\n- `string_manager.sh` - 字符串资源文件综合管理工具\n- `position_check.sh` - 位置"
  },
  {
    "path": "i18n/build.gradle.kts",
    "chars": 688,
    "preview": "plugins {\n    kotlin(\"jvm\")\n}\n\nrepositories {\n    mavenCentral()\n}\n\njava {\n    toolchain {\n        languageVersion.set(J"
  },
  {
    "path": "i18n/position_check.sh",
    "chars": 3743,
    "preview": "#!/bin/bash\n\n# 位置比对脚本\n# 检查中英文配置文件中相同key的行号是否一致\n\n# 颜色定义\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033["
  },
  {
    "path": "i18n/quick_check.sh",
    "chars": 1315,
    "preview": "#!/bin/bash\n\n# i18n 文件快速检查脚本\n# 功能:一键检查中英文文件同步性、重复项和行号一致性\n\n# 颜色定义\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'"
  },
  {
    "path": "i18n/src/main/kotlin/cn/netdiscovery/monica/i18n/Language.kt",
    "chars": 664,
    "preview": "package cn.netdiscovery.monica.i18n\n\n/**\n * 支持的语言枚举\n */\nenum class Language(val code: String, val displayName: String, v"
  },
  {
    "path": "i18n/src/main/kotlin/cn/netdiscovery/monica/i18n/LocalizationManager.kt",
    "chars": 4051,
    "preview": "package cn.netdiscovery.monica.i18n\n\nimport cn.netdiscovery.monica.config.category.ConfigCategoryManager\n\n/**\n * 国际化管理器\n"
  },
  {
    "path": "i18n/src/main/kotlin/cn/netdiscovery/monica/i18n/XmlStringResource.kt",
    "chars": 3468,
    "preview": "package cn.netdiscovery.monica.i18n\n\nimport org.w3c.dom.Document\nimport org.w3c.dom.Element\nimport java.io.InputStream\ni"
  },
  {
    "path": "i18n/src/main/resources/strings/strings_en.xml",
    "chars": 40076,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n    <!-- Main window and menu -->\n    <string name=\"app_name\">Monica<"
  },
  {
    "path": "i18n/src/main/resources/strings/strings_zh.xml",
    "chars": 32770,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n    <!-- 主窗口和菜单 -->\n    <string name=\"app_name\">Monica</string>\n    <"
  },
  {
    "path": "i18n/src/test/kotlin/cn/netdiscovery/monica/i18n/InternationalizationTest.kt",
    "chars": 3007,
    "preview": "package cn.netdiscovery.monica.i18n\n\nimport org.junit.Test\nimport org.junit.Assert.*\nimport org.junit.Before\n\n/**\n * 国际化"
  },
  {
    "path": "i18n/string_manager.sh",
    "chars": 15475,
    "preview": "#!/bin/bash\n\n# 字符串资源文件综合管理工具\n# 功能:检查重复项、清理重复项、比对中英文文件\n# 作者: AI Assistant\n# 版本: 2.0\n\n# 颜色定义\nRED='\\033[0;31m'\nGREEN='\\033["
  },
  {
    "path": "imageprocess/build.gradle.kts",
    "chars": 1397,
    "preview": "plugins {\n    kotlin(\"jvm\")\n}\n\nrepositories {\n    mavenCentral()\n    maven( \"https://jitpack.io\" )\n}\n\ndependencies {\n   "
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/BufferedImages.kt",
    "chars": 1364,
    "preview": "package cn.netdiscovery.monica.imageprocess\n\nimport java.awt.image.BufferedImage\n\n/**\n *\n * @FileName:\n *          cn.ne"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/Colormap.kt",
    "chars": 403,
    "preview": "package cn.netdiscovery.monica.imageprocess\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.Colorm"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/IntIntegralImage.kt",
    "chars": 3201,
    "preview": "package cn.netdiscovery.monica.imageprocess\n\nimport java.util.*\n\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monic"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/Transformer.kt",
    "chars": 322,
    "preview": "package cn.netdiscovery.monica.imageprocess\n\nimport java.awt.image.BufferedImage\n\n/**\n *\n * @FileName:\n *          cn.ne"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/domain/ArrayColormap.kt",
    "chars": 3176,
    "preview": "package cn.netdiscovery.monica.imageprocess.domain\n\nimport cn.netdiscovery.monica.imageprocess.Colormap\nimport cn.netdis"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/domain/Gradient.kt",
    "chars": 14132,
    "preview": "package cn.netdiscovery.monica.imageprocess.domain\n\nimport cn.netdiscovery.monica.imageprocess.math.Noise.Companion.lerp"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/domain/Histogram.kt",
    "chars": 3907,
    "preview": "package cn.netdiscovery.monica.imageprocess.domain\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/BilateralFilter.kt",
    "chars": 5443,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/BlockFilter.kt",
    "chars": 1844,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/BumpFilter.kt",
    "chars": 523,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ConvolveFilte"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/CarveFilter.kt",
    "chars": 1706,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorProcesso"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/CellularFilter.kt",
    "chars": 11640,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.Colormap\nimport cn.netdis"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/ColorFilter.kt",
    "chars": 1064,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorProcesso"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/ColorHalftoneFilter.kt",
    "chars": 4142,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/ConBriFilter.kt",
    "chars": 2452,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorProcesso"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/CropFilter.kt",
    "chars": 905,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/CrystallizeFilter.kt",
    "chars": 1930,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.math.mixColors\nimport cn."
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/DiffuseFilter.kt",
    "chars": 1232,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransformFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/EmbossFilter.kt",
    "chars": 1770,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorProcesso"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/EqualizeFilter.kt",
    "chars": 2007,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.domain.Histogram\nimport c"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/ExposureFilter.kt",
    "chars": 513,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransferFilte"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/GainFilter.kt",
    "chars": 644,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransferFilte"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/GammaFilter.kt",
    "chars": 1606,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/GaussianNoiseFilter.kt",
    "chars": 1225,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorProcesso"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/GradientFilter.kt",
    "chars": 4771,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/GrayFilter.kt",
    "chars": 951,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/HSBAdjustFilter.kt",
    "chars": 1274,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.PointFilter\ni"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/HighPassFilter.kt",
    "chars": 1714,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.blur.GaussianFilte"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/InvertFilter.kt",
    "chars": 541,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.PointFilter\n\n"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/MarbleFilter.kt",
    "chars": 1510,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransformFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/MirrorFilter.kt",
    "chars": 1327,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/MosaicFilter.kt",
    "chars": 2129,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.IntIntegralImage\nimport c"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/NatureFilter.kt",
    "chars": 4129,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorProcesso"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/OffsetFilter.kt",
    "chars": 1177,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransformFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/OilPaintFilter.kt",
    "chars": 3213,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/PointillizeFilter.kt",
    "chars": 1951,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.math.mixColors\nimport cn."
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/PosterizeFilter.kt",
    "chars": 893,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.PointFilter\n\n"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/RippleFilter.kt",
    "chars": 2250,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransformFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/SepiaToneFilter.kt",
    "chars": 1709,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/SmearFilter.kt",
    "chars": 7204,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.WholeImageFil"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/SolarizeFilter.kt",
    "chars": 461,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransferFilte"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/SpotlightFilter.kt",
    "chars": 1932,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/StrokeAreaFilter.kt",
    "chars": 3523,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/SwimFilter.kt",
    "chars": 1526,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransformFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/VignetteFilter.kt",
    "chars": 3154,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/WaterFilter.kt",
    "chars": 1919,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.TransformFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/WhiteImageFilter.kt",
    "chars": 1410,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilter\nim"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/base/BaseFilter.kt",
    "chars": 2257,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.base\n\nimport cn.netdiscovery.monica.imageprocess.BufferedImages\nimpor"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/base/ColorProcessorFilter.kt",
    "chars": 2388,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.base\n\nimport cn.netdiscovery.monica.imageprocess.BufferedImages\nimpor"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/base/ConvolveFilter.kt",
    "chars": 10066,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.base\n\n\nimport cn.netdiscovery.monica.imageprocess.utils.clamp\nimport "
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/base/PointFilter.kt",
    "chars": 1003,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.base\n\nimport java.awt.image.BufferedImage\n\n/**\n *\n * @FileName:\n *   "
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/base/TransferFilter.kt",
    "chars": 1426,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.base\n\nimport cn.netdiscovery.monica.imageprocess.utils.clamp\n\n\n/**\n *"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/base/TransformFilter.kt",
    "chars": 7039,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.base\n\nimport cn.netdiscovery.monica.imageprocess.math.bilinearInterpo"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/base/WholeImageFilter.kt",
    "chars": 1796,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.base\n\nimport java.awt.Rectangle\nimport java.awt.image.BufferedImage\ni"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/AverageFilter.kt",
    "chars": 522,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.Convolve"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/BoxBlurFilter.kt",
    "chars": 2894,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/FastBlur2D.kt",
    "chars": 3181,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.IntIntegralImage\nimp"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/GaussianFilter.kt",
    "chars": 5451,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/LensBlurFilter.kt",
    "chars": 8056,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/MaximumFilter.kt",
    "chars": 2218,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorPro"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/MinimumFilter.kt",
    "chars": 2192,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.ColorPro"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/MotionFilter.kt",
    "chars": 3498,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/blur/VariableBlurFilter.kt",
    "chars": 4424,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.blur\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.BaseFilt"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/sharpen/LaplaceSharpenFilter.kt",
    "chars": 550,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.sharpen\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.Convo"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/sharpen/SharpenFilter.kt",
    "chars": 549,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.sharpen\n\nimport cn.netdiscovery.monica.imageprocess.filter.base.Convo"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/filter/sharpen/USMFilter.kt",
    "chars": 2114,
    "preview": "package cn.netdiscovery.monica.imageprocess.filter.sharpen\n\nimport cn.netdiscovery.monica.imageprocess.filter.blur.Gauss"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/AutumnLUT.kt",
    "chars": 8607,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/BoneLUT.kt",
    "chars": 8884,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/CoolLUT.kt",
    "chars": 9003,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/HotLUT.kt",
    "chars": 8497,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/HsvLUT.kt",
    "chars": 8598,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/JetLUT.kt",
    "chars": 8598,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/LUT.kt",
    "chars": 1179,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/OceanLUT.kt",
    "chars": 8497,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/PinkLUT.kt",
    "chars": 9075,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/RainbowLUT.kt",
    "chars": 8579,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/SpringLUT.kt",
    "chars": 9007,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/SummerLUT.kt",
    "chars": 9119,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/lut/WinterLUT.kt",
    "chars": 8606,
    "preview": "package cn.netdiscovery.monica.imageprocess.lut\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.lu"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/math/FFT.kt",
    "chars": 4298,
    "preview": "package cn.netdiscovery.monica.imageprocess.math\n\nimport kotlin.math.max\nimport kotlin.math.sin\n\n/**\n *\n * @FileName:\n *"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/math/Functions.kt",
    "chars": 423,
    "preview": "package cn.netdiscovery.monica.imageprocess.math\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.m"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/math/ImageMath.kt",
    "chars": 4988,
    "preview": "package cn.netdiscovery.monica.imageprocess.math\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.imageprocess.u"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/math/Noise.kt",
    "chars": 10783,
    "preview": "package cn.netdiscovery.monica.imageprocess.math\n\nimport java.util.*\nimport kotlin.math.abs\nimport kotlin.math.max\nimpor"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/utils/ImageUtils.kt",
    "chars": 5869,
    "preview": "package cn.netdiscovery.monica.imageprocess.utils\n\nimport org.apache.batik.anim.dom.SAXSVGDocumentFactory\nimport org.apa"
  },
  {
    "path": "imageprocess/src/main/kotlin/cn/netdiscovery/monica/imageprocess/utils/extension/BufferedImage+Extensions.kt",
    "chars": 3873,
    "preview": "package cn.netdiscovery.monica.imageprocess.utils.extension\n\nimport cn.netdiscovery.monica.imageprocess.ImageInfo\nimport"
  },
  {
    "path": "opencv/build.gradle.kts",
    "chars": 330,
    "preview": "plugins {\n    kotlin(\"jvm\")\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation(kotlin(\"test\")"
  },
  {
    "path": "opencv/src/main/kotlin/cn/netdiscovery/monica/opencv/ImageProcess.kt",
    "chars": 6071,
    "preview": "package cn.netdiscovery.monica.opencv\n\nimport cn.netdiscovery.monica.config.isMac\nimport cn.netdiscovery.monica.config.i"
  },
  {
    "path": "resources/common/filterConfig.json",
    "chars": 12941,
    "preview": "[\n  {\n    \"name\": \"AverageFilter\",\n    \"desc\": \"均值模糊滤镜 - 通过平均邻域像素值实现平滑效果,有效减少图像噪声\",\n    \"remark\": \"适用于图像降噪和预处理,效果温和自然\",\n"
  },
  {
    "path": "resources/package.json",
    "chars": 261,
    "preview": "{\n  \"name\": \"monica-web-screenshot\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Web screenshot service for Monica\",\n  \"main"
  },
  {
    "path": "resources/web-screenshot.js",
    "chars": 8474,
    "preview": "const { chromium } = require('playwright');\nconst fs = require('fs');\nconst path = require('path');\n\n// 解析命令行参数\nconst ar"
  },
  {
    "path": "settings.gradle.kts",
    "chars": 611,
    "preview": "pluginManagement {\n    repositories {\n        google()\n        gradlePluginPortal()\n        mavenCentral()\n        maven"
  },
  {
    "path": "src/jvmMain/kotlin/Main.kt",
    "chars": 16826,
    "preview": "import androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime."
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/config/Constant.kt",
    "chars": 661,
    "preview": "package cn.netdiscovery.monica.config\n\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport cn.n"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/di/appModule.kt",
    "chars": 2624,
    "preview": "package cn.netdiscovery.monica.di\n\nimport org.koin.core.module.dsl.singleOf\nimport org.koin.dsl.module\nimport cn.netdisc"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/AppError.kt",
    "chars": 1617,
    "preview": "package cn.netdiscovery.monica.exception\n\n/**\n * 应用错误类\n * @FileName:\n *          cn.netdiscovery.monica.exception.AppErr"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/ErrorComposable.kt",
    "chars": 2623,
    "preview": "package cn.netdiscovery.monica.exception\n\nimport androidx.compose.material.AlertDialog\nimport androidx.compose.material."
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/ErrorExtensions.kt",
    "chars": 2964,
    "preview": "package cn.netdiscovery.monica.exception\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\n/"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/ErrorHandler.kt",
    "chars": 348,
    "preview": "package cn.netdiscovery.monica.exception\n\n/**\n * 错误处理器接口\n * @FileName:\n *          cn.netdiscovery.monica.exception.Erro"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/ErrorManager.kt",
    "chars": 2215,
    "preview": "package cn.netdiscovery.monica.exception\n\nimport cn.netdiscovery.monica.utils.logger\n\n/**\n * 错误管理器\n * @FileName:\n *     "
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/ErrorState.kt",
    "chars": 1951,
    "preview": "package cn.netdiscovery.monica.exception\n\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/Errors.kt",
    "chars": 1062,
    "preview": "package cn.netdiscovery.monica.exception\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.exception.Errors\n * @a"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/MonicaException.kt",
    "chars": 457,
    "preview": "package cn.netdiscovery.monica.exception\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.exception.MonicaExcept"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/handlers/AIServiceErrorHandler.kt",
    "chars": 901,
    "preview": "package cn.netdiscovery.monica.exception.handlers\n\nimport cn.netdiscovery.monica.exception.*\n\n/**\n * AI服务错误处理器\n * @FileN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/handlers/FileIOErrorHandler.kt",
    "chars": 838,
    "preview": "package cn.netdiscovery.monica.exception.handlers\n\nimport cn.netdiscovery.monica.exception.*\n\n/**\n * 文件IO错误处理器\n * @FileN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/handlers/ImageProcessingErrorHandler.kt",
    "chars": 859,
    "preview": "package cn.netdiscovery.monica.exception.handlers\n\nimport cn.netdiscovery.monica.exception.*\n\n/**\n * 图像处理错误处理器\n * @FileN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/handlers/NetworkErrorHandler.kt",
    "chars": 892,
    "preview": "package cn.netdiscovery.monica.exception.handlers\n\nimport cn.netdiscovery.monica.exception.*\n\n/**\n * 网络错误处理器\n * @FileNam"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/exception/handlers/ValidationErrorHandler.kt",
    "chars": 849,
    "preview": "package cn.netdiscovery.monica.exception.handlers\n\nimport cn.netdiscovery.monica.exception.*\n\n/**\n * 验证错误处理器\n * @FileNam"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/history/EditHistoryCenter.kt",
    "chars": 1493,
    "preview": "package cn.netdiscovery.monica.history\n\nimport cn.netdiscovery.monica.config.KEY_GENERAL_SETTINGS\nimport cn.netdiscovery"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/history/EditHistoryManager.kt",
    "chars": 3690,
    "preview": "package cn.netdiscovery.monica.history\n\nimport cn.netdiscovery.monica.utils.logger\nimport kotlinx.coroutines.*\nimport or"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/history/HistoryEntry.kt",
    "chars": 543,
    "preview": "package cn.netdiscovery.monica.history\n\nimport java.util.*\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.hist"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/history/modules/colorcorrection/ColorCorrectionParams.kt",
    "chars": 3254,
    "preview": "package cn.netdiscovery.monica.history.modules.colorcorrection\n\nimport cn.netdiscovery.monica.config.MODULE_COLOR\nimport"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/history/modules/opencv/CVParams.kt",
    "chars": 908,
    "preview": "package cn.netdiscovery.monica.history.modules.opencv\n\nimport cn.netdiscovery.monica.config.MODULE_OPENCV\nimport cn.netd"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/http/GsonSerializer.kt",
    "chars": 531,
    "preview": "package cn.netdiscovery.monica.http\n\nimport cn.netdiscovery.http.core.serializer.Serializer\nimport com.google.gson.Gson\n"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/http/HttpClient.kt",
    "chars": 3505,
    "preview": "package cn.netdiscovery.monica.http\n\nimport cn.netdiscovery.http.core.HttpClientBuilder\nimport cn.netdiscovery.http.core"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/llm/DeepSeekRequest.kt",
    "chars": 386,
    "preview": "package cn.netdiscovery.monica.llm\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.llm.DeepSeekRequest\n * @auth"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/llm/DeepseekClient.kt",
    "chars": 3167,
    "preview": "package cn.netdiscovery.monica.llm\n\nimport cn.netdiscovery.http.core.utils.extension.asyncCall\nimport cn.netdiscovery.mo"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/llm/DialogSession.kt",
    "chars": 692,
    "preview": "package cn.netdiscovery.monica.llm\n\nimport cn.netdiscovery.monica.domain.ColorCorrectionSettings\n\n/**\n *\n * @FileName:\n "
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/llm/GeminiClient.kt",
    "chars": 3165,
    "preview": "package cn.netdiscovery.monica.llm\n\nimport cn.netdiscovery.http.core.utils.extension.asyncCall\nimport cn.netdiscovery.mo"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/llm/GeminiRequest.kt",
    "chars": 449,
    "preview": "package cn.netdiscovery.monica.llm\n\n/**\n *\n * @FileName:\n *          cn.netdiscovery.monica.llm.GeminiRequest\n * @author"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/llm/LLMServiceManager.kt",
    "chars": 3002,
    "preview": "package cn.netdiscovery.monica.llm\n\nimport androidx.compose.runtime.*\nimport cn.netdiscovery.monica.domain.ColorCorrecti"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/manager/OpenCVManager.kt",
    "chars": 2867,
    "preview": "package cn.netdiscovery.monica.manager\n\nimport cn.netdiscovery.monica.imageprocess.BufferedImages\nimport cn.netdiscovery"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/state/ApplicationState.kt",
    "chars": 6267,
    "preview": "package cn.netdiscovery.monica.state\n\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.awt.ComposeWindow\nimp"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/BasicView.kt",
    "chars": 8263,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel\n\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.runt"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/AIView.kt",
    "chars": 2676,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai\n\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/AIViewModel.kt",
    "chars": 2570,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai\n\nimport cn.netdiscovery.monica.exception.ErrorSeverity\nimport cn.netdi"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/BinaryImageView.kt",
    "chars": 16788,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.layout.Column\nimport an"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/CVState.kt",
    "chars": 2293,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.runtime.getValue\nimport androidx.c"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/ContourAnalysisView.kt",
    "chars": 14250,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.layout.*\nimport android"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/EdgeDetectionView.kt",
    "chars": 13184,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.layout.*\nimport android"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/ExperimentHome.kt",
    "chars": 1375,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.Image\nimport androidx.c"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/ExperimentView.kt",
    "chars": 10445,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.Image\nimport androidx.c"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/HistoryView.kt",
    "chars": 3301,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.lazy.*\nimport androidx."
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/ImageDenoisingView.kt",
    "chars": 8993,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.layout.Column\nimport an"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/ImageEnhanceView.kt",
    "chars": 9458,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.layout.Column\nimport an"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/MatchTemplateView.kt",
    "chars": 13902,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.Image\nimport androidx.c"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/MorphologicalOperationsView.kt",
    "chars": 7213,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.foundation.layout.*\nimport android"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/NavController.kt",
    "chars": 1834,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.runtime.Composable\nimport androidx"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/NavigationHost.kt",
    "chars": 1006,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment\n\nimport androidx.compose.runtime.Composable\n\n/**\n *\n * @Fil"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/BinaryImageViewModel.kt",
    "chars": 4413,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/ContourAnalysisViewModel.kt",
    "chars": 2540,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/EdgeDetectionViewModel.kt",
    "chars": 4702,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/HistoryViewModel.kt",
    "chars": 828,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/ImageDenoisingViewModel.kt",
    "chars": 3504,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/ImageEnhanceViewModel.kt",
    "chars": 4213,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/MatchTemplateViewModel.kt",
    "chars": 2321,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/experiment/viewmodel/MorphologicalOperationsViewModel.kt",
    "chars": 1986,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.experiment.viewmodel\n\nimport cn.netdiscovery.monica.config.MODULE_OPEN"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/faceswap/FaceSwapView.kt",
    "chars": 13535,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.faceswap\n\nimport androidx.compose.foundation.Image\nimport androidx.com"
  },
  {
    "path": "src/jvmMain/kotlin/cn/netdiscovery/monica/ui/controlpanel/ai/faceswap/FaceSwapViewModel.kt",
    "chars": 3932,
    "preview": "package cn.netdiscovery.monica.ui.controlpanel.ai.faceswap\n\nimport androidx.compose.runtime.getValue\nimport androidx.com"
  }
]

// ... and 151 more files (download for full content)

About this extraction

This page contains the full source code of the fengzhizi715/Monica GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 351 files (1.7 MB), approximately 445.8k tokens, and a symbol index with 5 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!